r13904 chris_y - /trunk/netsurf/amiga/font_scan.c
by netsurf@semichrome.net
Author: chris_y
Date: Sat May 5 09:46:18 2012
New Revision: 13904
URL: http://source.netsurf-browser.org?rev=13904&view=rev
Log:
Add loading, parse OS fonts into a list (which can optionally already
contain fonts) before scanning
Modified:
trunk/netsurf/amiga/font_scan.c
Modified: trunk/netsurf/amiga/font_scan.c
URL: http://source.netsurf-browser.org/trunk/netsurf/amiga/font_scan.c?rev=139...
==============================================================================
--- trunk/netsurf/amiga/font_scan.c (original)
+++ trunk/netsurf/amiga/font_scan.c Sat May 5 09:46:18 2012
@@ -31,6 +31,8 @@
#include <libwapcaplet/libwapcaplet.h>
+#include "amiga/object.h"
+
/**
* Scan a font for glyphs not present in glypharray.
*
@@ -45,6 +47,7 @@
struct GlyphWidthEntry *gwnode;
ULONG foundglyphs = 0;
ULONG serif = 0;
+ lwc_error lerror;
ofont = OpenOutlineFont(fontname, NULL, OFF_OPEN);
@@ -62,8 +65,8 @@
gwnode = (struct GlyphWidthEntry *)GetHead((struct List *)widthlist);
do {
if(gwnode && (glypharray[gwnode->gwe_Code] == NULL)) {
- lwc_intern_string(fontname, strlen(fontname) - 5, &glypharray[gwnode->gwe_Code]);
- //printf("%lx\n",gwnode->gwe_Code);
+ lerror = lwc_intern_string(fontname, strlen(fontname) - 5, &glypharray[gwnode->gwe_Code]);
+ if(lerror != lwc_error_ok) continue;
foundglyphs++;
}
} while(gwnode = (struct GlyphWidthEntry *)GetSucc((struct Node *)gwnode));
@@ -81,13 +84,41 @@
* \param glypharray an array of 0xffff lwc_string pointers
* \return number of glyphs found
*/
-ULONG ami_font_scan_fonts(lwc_string **glypharray)
-{
-/* TODO: this function should take a list of fonts and add to it, ignoring duplicates */
+ULONG ami_font_scan_fonts(struct MinList *list, lwc_string **glypharray)
+{
+ ULONG found, total = 0;
+ struct nsObject *node;
+ struct nsObject *nnode;
+
+ if(IsMinListEmpty(list)) return 0;
+
+ node = (struct nsObject *)GetHead((struct List *)list);
+
+ do {
+ nnode = (struct nsObject *)GetSucc((struct Node *)node);
+ printf("Scanning %s...\n", node->dtz_Node.ln_Name);
+ found = ami_font_scan_font(node->dtz_Node.ln_Name, glypharray);
+ total += found;
+ printf("Found %ld new glyphs (total = %ld)\n", found, total);
+ } while(node = nnode);
+
+ return total;
+}
+
+
+/**
+ * Add OS fonts to a list.
+ *
+ * \param list list to add font names to
+ * \return number of fonts found
+ */
+ULONG ami_font_scan_list(struct MinList *list)
+{
int afShortage, afSize = 100, i;
struct AvailFontsHeader *afh;
struct AvailFonts *af;
- ULONG found, total = 0;
+ ULONG found;
+ struct nsObject *node;
printf("Scanning fonts...\n");
do {
@@ -105,13 +136,19 @@
if(afh) {
af = (struct AvailFonts *)&(afh[1]);
printf("af = %lx entries = %ld\n", af, afh->afh_NumEntries);
-/* bug somewhere, this only does 36 fonts as size 0 */
+
for(i = 0; i < afh->afh_NumEntries; i++) {
- if((af[i].af_Attr.ta_YSize == 0) && (af[i].af_Attr.ta_Style == FS_NORMAL)) {
- printf("%s (%ld) %ld\n", af[i].af_Attr.ta_Name, af[i].af_Attr.ta_Style, af[i].af_Attr.ta_YSize);
- found = ami_font_scan_font(af[i].af_Attr.ta_Name, glypharray);
- total += found;
- printf("Found %ld new glyphs (total = %ld)\n", found, total);
+ if(af[i].af_Attr.ta_Style == FS_NORMAL) {
+ node = (struct nsObject *)FindIName((struct List *)list,
+ af[i].af_Attr.ta_Name);
+ if(node == NULL) {
+ node = AddObject(list, AMINS_UNKNOWN);
+ if(node) {
+ node->dtz_Node.ln_Name = strdup(af[i].af_Attr.ta_Name);
+ found++;
+ printf("Added %s\n", af[i].af_Attr.ta_Name);
+ }
+ }
}
af++;
}
@@ -119,6 +156,7 @@
} else {
return 0;
}
+ return found;
}
/**
@@ -132,10 +170,43 @@
{
ULONG found = 0;
BPTR fh = 0;
+ lwc_error lerror;
+ char buffer[256];
+ struct RDArgs *rargs = NULL;
+ STRPTR template = "CODE/A,FONT/A";
+ long rarray[] = {0,0};
+
+ enum {
+ A_CODE = 0,
+ A_FONT
+ };
+
+ rargs = AllocDosObjectTags(DOS_RDARGS, TAG_DONE);
if(fh = FOpen(filename, MODE_OLDFILE, 0)) {
printf("Reading %s\n", filename);
- /* TODO: read lines using ReadArgs() */
+
+ while(FGets(fh, (UBYTE *)&buffer, 256) != 0)
+ {
+ rargs->RDA_Source.CS_Buffer = (char *)&buffer;
+ rargs->RDA_Source.CS_Length = 256;
+ rargs->RDA_Source.CS_CurChr = 0;
+
+ rargs->RDA_DAList = NULL;
+ rargs->RDA_Buffer = NULL;
+ rargs->RDA_BufSiz = 0;
+ rargs->RDA_ExtHelp = NULL;
+ rargs->RDA_Flags = 0;
+
+ if(ReadArgs(template, rarray, rargs))
+ {
+ lerror = lwc_intern_string((const char *)rarray[A_FONT],
+ strlen((const char *)rarray[A_FONT]),
+ &glypharray[strtoul(rarray[A_CODE], NULL, 0)]);
+ if(lerror != lwc_error_ok) continue;
+ found++;
+ }
+ }
FClose(fh);
}
@@ -162,7 +233,7 @@
for(i=0x0000; i<=0xffff; i++)
{
if(glypharray[i]) {
- FPrintf(fh, "%04lx \"%s\"\n", i, lwc_string_data(glypharray[i]));
+ FPrintf(fh, "0x%04lx \"%s\"\n", i, lwc_string_data(glypharray[i]));
lwc_string_unref(glypharray[i]);
}
}
@@ -195,9 +266,9 @@
* \param filename cache file to attempt to read
* \param glypharray an array of 0xffff lwc_string pointers
*/
-void ami_font_scan_init(const char *filename, lwc_string **glypharray)
-{
- ULONG i, found;
+void ami_font_scan_init(const char *filename, struct MinList *list, lwc_string **glypharray)
+{
+ ULONG i, found, ffound;
/* Ensure array zeroed */
for(i=0x0000; i<=0xffff; i++)
@@ -206,7 +277,9 @@
found = ami_font_scan_load(filename, glypharray);
if(found == 0) {
- found = ami_font_scan_fonts(glypharray);
+ ffound = ami_font_scan_list(list);
+ printf("Found %ld system fonts\n", ffound);
+ found = ami_font_scan_fonts(list, glypharray);
ami_font_scan_save(filename, glypharray);
}
@@ -215,21 +288,30 @@
#ifdef AMI_FONT_SCAN_STANDALONE
/* This can be compiled as standalone using:
-* gcc -o font_scan font_scan.c -lwapcaplet -lauto -D__USE_INLINE__ -DAMI_FONT_SCAN_STANDALONE
+* gcc -o font_scan font_scan.c object.c -lwapcaplet -lauto -I .. -D__USE_INLINE__ -DAMI_FONT_SCAN_STANDALONE
*/
int main(int argc, char** argv)
{
lwc_string *glypharray[0xffff + 1];
ULONG found = 0;
BPTR fh;
+ struct MinList *list;
if(argc < 2) return 5;
printf("%s\n",argv[1]);
- ami_font_scan_init(argv[1], glypharray);
+ list = NewObjList();
+
+ ami_font_scan_init(argv[1], list, glypharray);
ami_font_scan_fini(glypharray);
+ FreeObjList(list);
+
return 0;
}
+
+void ami_font_close(APTR discard) { }
+void ami_mime_entry_free(APTR discard) { }
+
#endif
10 years, 9 months
r13903 chris_y - /trunk/netsurf/amiga/font_scan.c
by netsurf@semichrome.net
Author: chris_y
Date: Sat May 5 06:09:19 2012
New Revision: 13903
URL: http://source.netsurf-browser.org?rev=13903&view=rev
Log:
Tidy-up, split into sensible functions for NetSurf integration,
documentation. Cache file loading needs adding and ami_font_scan_fonts
needs to add to a list of fonts, and then send those to the scanner so we
can put some preferred fonts on the top of the list and avoid duplication.
Modified:
trunk/netsurf/amiga/font_scan.c
Modified: trunk/netsurf/amiga/font_scan.c
URL: http://source.netsurf-browser.org/trunk/netsurf/amiga/font_scan.c?rev=139...
==============================================================================
--- trunk/netsurf/amiga/font_scan.c (original)
+++ trunk/netsurf/amiga/font_scan.c Sat May 5 06:09:19 2012
@@ -16,8 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-/* scan fonts
-* gcc -o scan_font font_scan.c -lwapcaplet -lauto -D__USE_INLINE__
+/** \file
+ * Font glyph scanner for Unicode substitutions.
*/
#include <stdio.h>
@@ -31,7 +31,14 @@
#include <libwapcaplet/libwapcaplet.h>
-ULONG scan_font(const char *fontname, lwc_string **glypharray)
+/**
+ * Scan a font for glyphs not present in glypharray.
+ *
+ * \param fontname font to scan
+ * \param glypharray an array of 0xffff lwc_string pointers
+ * \return number of new glyphs found
+ */
+ULONG ami_font_scan_font(const char *fontname, lwc_string **glypharray)
{
struct OutlineFont *ofont;
struct MinList *widthlist;
@@ -55,7 +62,7 @@
gwnode = (struct GlyphWidthEntry *)GetHead((struct List *)widthlist);
do {
if(gwnode && (glypharray[gwnode->gwe_Code] == NULL)) {
- lwc_intern_string(fontname, strlen(fontname), &glypharray[gwnode->gwe_Code]);
+ lwc_intern_string(fontname, strlen(fontname) - 5, &glypharray[gwnode->gwe_Code]);
//printf("%lx\n",gwnode->gwe_Code);
foundglyphs++;
}
@@ -68,8 +75,15 @@
return foundglyphs;
}
-ULONG scan_fonts(lwc_string **glypharray)
-{
+/**
+ * Scan all fonts for glyphs.
+ *
+ * \param glypharray an array of 0xffff lwc_string pointers
+ * \return number of glyphs found
+ */
+ULONG ami_font_scan_fonts(lwc_string **glypharray)
+{
+/* TODO: this function should take a list of fonts and add to it, ignoring duplicates */
int afShortage, afSize = 100, i;
struct AvailFontsHeader *afh;
struct AvailFonts *af;
@@ -95,7 +109,7 @@
for(i = 0; i < afh->afh_NumEntries; i++) {
if((af[i].af_Attr.ta_YSize == 0) && (af[i].af_Attr.ta_Style == FS_NORMAL)) {
printf("%s (%ld) %ld\n", af[i].af_Attr.ta_Name, af[i].af_Attr.ta_Style, af[i].af_Attr.ta_YSize);
- found = scan_font(af[i].af_Attr.ta_Name, glypharray);
+ found = ami_font_scan_font(af[i].af_Attr.ta_Name, glypharray);
total += found;
printf("Found %ld new glyphs (total = %ld)\n", found, total);
}
@@ -107,36 +121,115 @@
}
}
-int main(int argc, char** argv)
-{
- lwc_string *glypharray[0xffff + 1];
- ULONG i, found = 0;
- BPTR fh;
-
- if(argc < 2) return 5;
-
- printf("%s\n",argv[1]);
-
- /* Ensure array zeroed */
- for(i=0x0000; i<=0xffff; i++)
- glypharray[i] = NULL;
-
- scan_fonts(glypharray);
-
- if(fh = FOpen(argv[1], ACCESS_WRITE, 0)) {
- printf("Writing %s\n", argv[1]);
+/**
+ * Load a font glyph cache
+ *
+ * \param filename name of cache file to load
+ * \param glypharray an array of 0xffff lwc_string pointers
+ * \return number of glyphs loaded
+ */
+ULONG ami_font_scan_load(const char *filename, lwc_string **glypharray)
+{
+ ULONG found = 0;
+ BPTR fh = 0;
+
+ if(fh = FOpen(filename, MODE_OLDFILE, 0)) {
+ printf("Reading %s\n", filename);
+ /* TODO: read lines using ReadArgs() */
+ FClose(fh);
+ }
+
+ return found;
+}
+
+/**
+ * Save a font glyph cache
+ *
+ * \param filename name of cache file to save
+ * \param glypharray an array of 0xffff lwc_string pointers
+ */
+void ami_font_scan_save(const char *filename, lwc_string **glypharray)
+{
+ ULONG i;
+ BPTR fh = 0;
+
+ if(fh = FOpen(filename, MODE_NEWFILE, 0)) {
+ printf("Writing %s\n", filename);
+ FPrintf(fh, "; This file is auto-generated. To recreate the cache, delete this file.\n");
+ FPrintf(fh, "; This file is parsed using ReadArgs() with the following template:\n");
+ FPrintf(fh, "; CODE/A,FONT/A\n;\n");
+
for(i=0x0000; i<=0xffff; i++)
{
if(glypharray[i]) {
FPrintf(fh, "%04lx \"%s\"\n", i, lwc_string_data(glypharray[i]));
lwc_string_unref(glypharray[i]);
- found++;
}
}
FClose(fh);
}
-
- printf("Found %ld glyphs\n", found);
+}
+
+/**
+ * Finalise the font glyph cache.
+ *
+ * \param glypharray an array of 0xffff lwc_string pointers to free
+ */
+void ami_font_scan_fini(lwc_string **glypharray)
+{
+ ULONG i;
+
+ for(i=0x0000; i<=0xffff; i++)
+ {
+ if(glypharray[i]) {
+ lwc_string_unref(glypharray[i]);
+ glypharray[i] = NULL;
+ }
+ }
+}
+
+/**
+ * Initialise the font glyph cache.
+ * Reads an existing file or, if not present, generates a new cache.
+ *
+ * \param filename cache file to attempt to read
+ * \param glypharray an array of 0xffff lwc_string pointers
+ */
+void ami_font_scan_init(const char *filename, lwc_string **glypharray)
+{
+ ULONG i, found;
+
+ /* Ensure array zeroed */
+ for(i=0x0000; i<=0xffff; i++)
+ glypharray[i] = NULL;
+
+ found = ami_font_scan_load(filename, glypharray);
+
+ if(found == 0) {
+ found = ami_font_scan_fonts(glypharray);
+ ami_font_scan_save(filename, glypharray);
+ }
+
+ printf("Initialised with %ld glyphs\n", found);
+}
+
+#ifdef AMI_FONT_SCAN_STANDALONE
+/* This can be compiled as standalone using:
+* gcc -o font_scan font_scan.c -lwapcaplet -lauto -D__USE_INLINE__ -DAMI_FONT_SCAN_STANDALONE
+*/
+int main(int argc, char** argv)
+{
+ lwc_string *glypharray[0xffff + 1];
+ ULONG found = 0;
+ BPTR fh;
+
+ if(argc < 2) return 5;
+
+ printf("%s\n",argv[1]);
+
+ ami_font_scan_init(argv[1], glypharray);
+ ami_font_scan_fini(glypharray);
return 0;
}
+#endif
10 years, 9 months
r13902 chris_y - /trunk/netsurf/amiga/font_scan.c
by netsurf@semichrome.net
Author: chris_y
Date: Fri May 4 14:30:30 2012
New Revision: 13902
URL: http://source.netsurf-browser.org?rev=13902&view=rev
Log:
Font glyph scanner. Currently works as a standalone program which outputs a
file for NetSurf to ignore.
Added:
trunk/netsurf/amiga/font_scan.c (with props)
Added: trunk/netsurf/amiga/font_scan.c
URL: http://source.netsurf-browser.org/trunk/netsurf/amiga/font_scan.c?rev=139...
==============================================================================
--- trunk/netsurf/amiga/font_scan.c (added)
+++ trunk/netsurf/amiga/font_scan.c Fri May 4 14:30:30 2012
@@ -1,0 +1,142 @@
+/*
+ * Copyright 2012 Chris Young <chris(a)unsatisfactorysoftware.co.uk>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* scan fonts
+* gcc -o scan_font font_scan.c -lwapcaplet -lauto -D__USE_INLINE__
+*/
+
+#include <stdio.h>
+#include <string.h>
+
+#include <proto/diskfont.h>
+#include <proto/dos.h>
+#include <proto/exec.h>
+#include <diskfont/diskfonttag.h>
+#include <diskfont/oterrors.h>
+
+#include <libwapcaplet/libwapcaplet.h>
+
+ULONG scan_font(const char *fontname, lwc_string **glypharray)
+{
+ struct OutlineFont *ofont;
+ struct MinList *widthlist;
+ struct GlyphWidthEntry *gwnode;
+ ULONG foundglyphs = 0;
+ ULONG serif = 0;
+
+ ofont = OpenOutlineFont(fontname, NULL, OFF_OPEN);
+
+ if(!ofont) return;
+
+ if(ESetInfo(&ofont->olf_EEngine,
+ OT_PointHeight, 10 * (1 << 16),
+ OT_GlyphCode, 0x0000,
+ OT_GlyphCode2, 0xffff,
+ TAG_END) == OTERR_Success)
+ {
+ if(EObtainInfo(&ofont->olf_EEngine,
+ OT_WidthList, &widthlist, TAG_END) == 0)
+ {
+ gwnode = (struct GlyphWidthEntry *)GetHead((struct List *)widthlist);
+ do {
+ if(gwnode && (glypharray[gwnode->gwe_Code] == NULL)) {
+ lwc_intern_string(fontname, strlen(fontname), &glypharray[gwnode->gwe_Code]);
+ //printf("%lx\n",gwnode->gwe_Code);
+ foundglyphs++;
+ }
+ } while(gwnode = (struct GlyphWidthEntry *)GetSucc((struct Node *)gwnode));
+ }
+ }
+
+ CloseOutlineFont(ofont, NULL);
+
+ return foundglyphs;
+}
+
+ULONG scan_fonts(lwc_string **glypharray)
+{
+ int afShortage, afSize = 100, i;
+ struct AvailFontsHeader *afh;
+ struct AvailFonts *af;
+ ULONG found, total = 0;
+
+ printf("Scanning fonts...\n");
+ do {
+ if(afh = (struct AvailFontsHeader *)AllocVec(afSize, MEMF_PRIVATE)) {
+ if(afShortage = AvailFonts(afh, afSize, AFF_DISK | AFF_OTAG | AFF_SCALED)) {
+ FreeVec(afh);
+ afSize += afShortage;
+ }
+ } else {
+ /* out of memory, bail out */
+ return 0;
+ }
+ } while (afShortage);
+
+ if(afh) {
+ af = (struct AvailFonts *)&(afh[1]);
+printf("af = %lx entries = %ld\n", af, afh->afh_NumEntries);
+/* bug somewhere, this only does 36 fonts as size 0 */
+ for(i = 0; i < afh->afh_NumEntries; i++) {
+ if((af[i].af_Attr.ta_YSize == 0) && (af[i].af_Attr.ta_Style == FS_NORMAL)) {
+ printf("%s (%ld) %ld\n", af[i].af_Attr.ta_Name, af[i].af_Attr.ta_Style, af[i].af_Attr.ta_YSize);
+ found = scan_font(af[i].af_Attr.ta_Name, glypharray);
+ total += found;
+ printf("Found %ld new glyphs (total = %ld)\n", found, total);
+ }
+ af++;
+ }
+ FreeVec(afh);
+ } else {
+ return 0;
+ }
+}
+
+int main(int argc, char** argv)
+{
+ lwc_string *glypharray[0xffff + 1];
+ ULONG i, found = 0;
+ BPTR fh;
+
+ if(argc < 2) return 5;
+
+ printf("%s\n",argv[1]);
+
+ /* Ensure array zeroed */
+ for(i=0x0000; i<=0xffff; i++)
+ glypharray[i] = NULL;
+
+ scan_fonts(glypharray);
+
+ if(fh = FOpen(argv[1], ACCESS_WRITE, 0)) {
+ printf("Writing %s\n", argv[1]);
+ for(i=0x0000; i<=0xffff; i++)
+ {
+ if(glypharray[i]) {
+ FPrintf(fh, "%04lx \"%s\"\n", i, lwc_string_data(glypharray[i]));
+ lwc_string_unref(glypharray[i]);
+ found++;
+ }
+ }
+ FClose(fh);
+ }
+
+ printf("Found %ld glyphs\n", found);
+
+ return 0;
+}
Propchange: trunk/netsurf/amiga/font_scan.c
------------------------------------------------------------------------------
amiga:protection = ----rw-d ---- ----
10 years, 9 months
r13901 chris_y - /trunk/netsurf/amiga/font.c
by netsurf@semichrome.net
Author: chris_y
Date: Wed May 2 13:29:59 2012
New Revision: 13901
URL: http://source.netsurf-browser.org?rev=13901&view=rev
Log:
Ensure we are using the faster(?) character size functions in all
applicable places.
Modified:
trunk/netsurf/amiga/font.c
Modified: trunk/netsurf/amiga/font.c
URL: http://source.netsurf-browser.org/trunk/netsurf/amiga/font.c?rev=13901&r1...
==============================================================================
--- trunk/netsurf/amiga/font.c (original)
+++ trunk/netsurf/amiga/font.c Wed May 2 13:29:59 2012
@@ -724,14 +724,23 @@
utf16charsc = ami_font_translate_smallcaps(*utf16);
utf16nextsc = ami_font_translate_smallcaps(utf16next);
- tempx = ami_font_plot_glyph(ofont, rp, utf16charsc, utf16nextsc, dx + x, dy, emwidth);
+ if(rp) {
+ tempx = ami_font_plot_glyph(ofont, rp, utf16charsc, utf16nextsc, dx + x, dy, emwidth);
+ } else {
+ tempx = ami_font_width_glyph(ofont, utf16charsc, utf16nextsc, emwidth);
+ }
}
else tempx = 0;
+ if(tempx == 0) {
+ if(rp) {
+ tempx = ami_font_plot_glyph(ofont, rp, *utf16, utf16next, dx + x, dy, emwidth);
+ } else {
+ tempx = ami_font_width_glyph(ofont, *utf16, utf16next, emwidth);
+ }
+ }
+
if(tempx == 0)
- tempx = ami_font_plot_glyph(ofont, rp, *utf16, utf16next, dx + x, dy, emwidth);
-
- if(tempx == 0)
{
if(ufont == NULL)
{
@@ -740,14 +749,22 @@
if(ufont)
{
- tempx = ami_font_plot_glyph(ufont, rp, *utf16, utf16next,
- dx + x, dy, emwidth);
+ if(rp) {
+ tempx = ami_font_plot_glyph(ufont, rp, *utf16, utf16next,
+ dx + x, dy, emwidth);
+ } else {
+ tempx = ami_font_width_glyph(ufont, *utf16, utf16next, emwidth);
+ }
}
/*
if(tempx == 0)
{
- tempx = ami_font_plot_glyph(ofont, rp, 0xfffd, utf16next,
- dx + x, dy, emwidth);
+ if(rp) {
+ tempx = ami_font_plot_glyph(ofont, rp, 0xfffd, utf16next,
+ dx + x, dy, emwidth);
+ } else {
+ tempx = ami_font_width_glyph(ofont, 0xfffd, utf16next, emwidth);
+ }
}
*/
}
10 years, 9 months