summaryrefslogtreecommitdiffstats
path: root/src/extension/internal/text_reassemble.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/extension/internal/text_reassemble.c')
-rw-r--r--src/extension/internal/text_reassemble.c2973
1 files changed, 2973 insertions, 0 deletions
diff --git a/src/extension/internal/text_reassemble.c b/src/extension/internal/text_reassemble.c
new file mode 100644
index 0000000..543106f
--- /dev/null
+++ b/src/extension/internal/text_reassemble.c
@@ -0,0 +1,2973 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * text_reassemble.c from libTERE
+ *//*
+ * Authors: see below
+ *
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2.0+, read the file 'COPYING' for more information.
+ */
+
+
+/**
+ @file text_reassemble.c
+
+\verbatim
+Method:
+ 1. For all ordered text objects which are sequential and share the same esc.
+ 2. For the first only pull x,y,esc and save, these define origin and rotation.
+ 3. Save the text object.
+ 4. Phase I: For all saved text objects construct lines.
+ 5. Check for allowed overlaps on sequential saved text object bounding rectangles.
+ 6 If found merge second with first, check next one.
+ 7. If not found, start a new complex (line).
+ 8. Phase II; for all lines construct paragraphs.
+ 9. Check alignment and line spacing of preceding line with current line.
+ 10. if alignment is the same, and line spacing is compatible merge current line into
+ current paragraph. Reaverage line spacing over all lines in paragraph. Check next one.
+ 11. If alignment does not match start a new paragraph.
+ (Test program)
+ 12. Over all phase II paragraphs
+ 13. Over all phase I lines in each paragraph.
+ 14. Over all text objects in each line.
+ Emit SVG corresponding to this construct to a file dump.svg.
+ (Conversion to other file types would be modeled on this example.)
+ 15. Clean up.
+ (General program)
+ Like for the Test program, but final representation may not be SVG.
+ Text object and bounding rectangle memory would all be released. If another set of
+ text will be processed then hang onto both Freetype and Fontconfig structures. If no
+ other text will be processed here, then also release Freetype structures. If the caller uses
+ Fontconfig elsewhere then do not release it, otherwise, do so.
+
+NOTE ON COORDINATES: x is positive to the right, y is positive down. So (0,0) is the upper left corner, and the
+lower left corner of a rectangle has a LARGER Y coordinate than the upper left. Ie, LL=(10,10) UR=(30,5) is typical.
+\endverbatim
+*/
+
+/*
+
+Compilation of test program (with all debugging output, but not loop testing):
+On Windows use:
+
+ gcc -Wall -DWIN32 -DTEST -DDBG_TR_PARA -DDBG_TR_INPUT \
+ -I. -I/c/progs/devlibs32/include -I/c/progs/devlibs32/include/freetype2\
+ -o text_reassemble text_reassemble.c uemf_utf.c \
+ -lfreetype6 -lfontconfig-1 -liconv -lm -L/c/progs/devlibs32/bin
+
+On Linux use:
+
+ gcc -Wall -DTEST -DDBG_TR_PARA -DDBG_TR_INPUT -I. -I/usr/include/freetype2 -o text_reassemble text_reassemble.c uemf_utf.c -lfreetype -lfontconfig -lm
+
+Compilation of object file only (Windows):
+
+ gcc -Wall -DWIN32 -c \
+ -I. -I/c/progs/devlibs32/include -I/c/progs/devlibs32/include/freetype2\
+ text_reassemble.c
+
+Compilation of object file only (Linux):
+ gcc -Wall -c -I. -I/usr/include/freetype2 text_reassemble.c
+
+
+Optional compiler switches for development:
+ -DDBG_TR_PARA draw bounding rectangles for paragraphs in SVG output
+ -DDBG_TR_INPUT draw input text and their bounding rectangles in SVG output
+ -DTEST build the test program
+ -DDBG_LOOP force the test program to cycle 5 times. Useful for finding
+ memory leaks. Output file is overwritten each time.
+
+
+File: text_reassemble.c
+Version: 0.0.18
+Date: 11-MAR-2016
+Author: David Mathog, Biology Division, Caltech
+email: mathog@caltech.edu
+Copyright: 2016 David Mathog and California Institute of Technology (Caltech)
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "text_reassemble.h"
+#include <3rdparty/libuemf/uemf_utf.h> /* For a couple of text functions. Exact copy from libUEMF. */
+#include <locale.h>
+#include <float.h>
+
+/* Code generated by make_ucd_mn_table.c using:
+ cat mnlist.txt | ./make_ucd_mn_table >generated.c
+*/
+#include <stdint.h>
+int is_mn_unicode(int test){
+#define MN_TEST_LIMIT 921600
+#define N_SPAGES 225
+#define N_CPAGES 192
+#define N_CPDATA 344
+#define C_PER_S 16
+
+ // clang-format off
+ uint8_t superpages[N_SPAGES]={
+ 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x06,
+ 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0B};
+
+ uint8_t cpages[N_CPAGES]={
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
+ 0x0E, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x00, 0x00,
+ 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x19, 0x00, 0x00,
+ 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x1C, 0x1D, 0x1E, 0x1F, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x21, 0x00,
+ 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x25, 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27,
+ 0x00, 0x28, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+ uint32_t cpage_data[N_CPDATA]={
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000000F8, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFE0000, 0xBFFFFFFF, 0x000000B6, 0x00000000,
+ 0x07FF0000, 0x00000000, 0xFFFFF800, 0x00010000, 0x00000000, 0x00000000, 0x9FC00000, 0x00003D9F,
+ 0x00020000, 0xFFFF0000, 0x000007FF, 0x00000000, 0x00000000, 0x0001FFC0, 0x00000000, 0x000FF800,
+ 0xFBC00000, 0x00003EEF, 0x0E000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x7FFFFFF0,
+ 0x00000007, 0x14000000, 0x00FE21FE, 0x0000000C, 0x00000002, 0x10000000, 0x0000201E, 0x0000000C,
+ 0x00000006, 0x10000000, 0x00023986, 0x00230000, 0x00000006, 0x10000000, 0x000021BE, 0x0000000C,
+ 0x00000002, 0x90000000, 0x0040201E, 0x0000000C, 0x00000004, 0x00000000, 0x00002001, 0x00000000,
+ 0x00000000, 0xC0000000, 0x00603DC1, 0x0000000C, 0x00000000, 0x90000000, 0x00003040, 0x0000000C,
+ 0x00000000, 0x00000000, 0x0000201E, 0x0000000C, 0x00000000, 0x00000000, 0x005C0400, 0x00000000,
+ 0x00000000, 0x07F20000, 0x00007F80, 0x00000000, 0x00000000, 0x1BF20000, 0x00003F00, 0x00000000,
+ 0x03000000, 0x02A00000, 0x00000000, 0x7FFE0000, 0xFEFFE0DF, 0x1FFFFFFF, 0x00000040, 0x00000000,
+ 0x00000000, 0x66FDE000, 0xC3000000, 0x001E0001, 0x20002064, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0xE0000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x001C0000, 0x001C0000, 0x000C0000, 0x000C0000, 0x00000000, 0x3FB00000, 0x200FFE40, 0x00000000,
+ 0x00003800, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000200, 0x00000000, 0x00000000,
+ 0x00000000, 0x0E040187, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x01800000, 0x00000000, 0x7F400000, 0x9FF81FE5, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x0000000F, 0x17D00000, 0x00000004, 0x000FF800, 0x00000003, 0x00000B3C, 0x00000000, 0x0003A340,
+ 0x00000000, 0x00CFF000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFF70000, 0x001021FD,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xF000007F,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x1FFF0000, 0x0001FFE2,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00038000,
+ 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF,
+ 0x00000000, 0x00003C00, 0x00000000, 0x00000000, 0x06000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x3FF08000, 0x80000000, 0x00000000, 0x00000000, 0x00030000,
+ 0x00000844, 0x00000060, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000010, 0x0003FFFF,
+ 0x00000000, 0x00003FC0, 0x0003FF80, 0x00000000, 0x00000007, 0x13C80000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00667E00, 0x00001008, 0x00000000, 0x00000000, 0xC19D0000, 0x00000002, 0x00403000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00002120,
+ 0x40000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x0000FFFF, 0x0000007F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x20000000,
+ 0x0000F06E, 0x87000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000002, 0xFF000000, 0x0000007F, 0x00000000, 0x00000003, 0x06780000, 0x00000000, 0x00000000,
+ 0x00000007, 0x001FEF80, 0x00000000, 0x00000000, 0x00000003, 0x7FC00000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00BF2800, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00078000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0xF8000380, 0x00000FE7, 0x00003C00, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x0000001C, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF};
+ // clang-format on
+
+ int result=0;
+
+ int spage_idx;
+ int cpage_row, cpage_column, cpage_idx;
+ int cd_row, cd_column, cd_idx, cd_bit;
+
+ if(test<MN_TEST_LIMIT){
+ spage_idx = test >> 12;
+ cpage_row = superpages[spage_idx];
+ cpage_column = (test>>8) & 15;
+ cpage_idx = C_PER_S*cpage_row + cpage_column;
+ cd_row = cpages[cpage_idx];
+ cd_column = test>>5 & 7;
+ cd_idx = 8*cd_row + cd_column;
+ cd_bit = test & 31;
+ result = cpage_data[cd_idx] & (1 << cd_bit);
+ }
+ return(result);
+}
+
+
+
+/**
+ \brief Find a (sub)string in a caseinvariant manner, used for locating "Narrow" in font name
+ \return Returns -1 if no match, else returns the position (numbered from 0) of the first character of the match.
+ \param string Text to search
+ \param sub Text to find
+*/
+int TR_findcasesub(const char *string, const char *sub){
+ int i,j;
+ int match=0;
+ for(i=0; string[i]; i++){
+ for(match=1,j=0; sub[j] && string[i+j]; j++){
+ if(toupper(sub[j]) != toupper(string[i+j])){
+ match=0;
+ break;
+ }
+ }
+ if(match && !sub[j])break; /* matched over the entire substring */
+ }
+ return((match ? i : -1));
+}
+
+/**
+ \brief Constrouct a fontspec from a TCHUNK_SPECS and a fontname
+ \return Returns NULL on error, new fontspec on success
+ \param tsp pointer to TCHUNK_SPECS to use for information
+ \param fontname Fontname to use in the new fontspec
+*/
+ /* construct a font name */
+char *TR_construct_fontspec(const TCHUNK_SPECS *tsp, const char *fontname){
+ int newlen = 128 + strlen(fontname); /* too big, but not by much */
+ char *newfs = NULL;
+ newfs = (char *) malloc(newlen);
+ sprintf(newfs,"%s:slant=%d:weight=%d:size=%f:width=%d",fontname,tsp->italics,tsp->weight,tsp->fs,(tsp->co ? 75 : tsp->condensed));
+ return(newfs);
+}
+
+
+/**
+ \brief Reconstrouct a fontspec by substituting a font name into an existing spec
+ \return Returns NULL on error, new fontspec on success
+ \param fontspec Original fontspec, only the name will be changed
+ \param fontname Fontname to substitute into the new fontspec
+*/
+ /* construct a font name */
+char *TR_reconstruct_fontspec(const char *fontspec, const char *fontname){
+ int colon;
+ int newlen = strlen(fontspec) + strlen(fontname) + 1; /* too big, but not by much */
+ char *newfs = NULL;
+ newfs = (char *) malloc(newlen);
+ colon = strcspn(fontspec,":");
+ if(colon){ sprintf(newfs,"%s%s",fontname,&fontspec[colon]); }
+ return(newfs);
+}
+
+/**
+ \brief Find a font in the list that has a glyph for this character, change alternate to match
+ \return Returns 0 if no match or an error, else returns the glyph index in the new alternate font
+ \param fti pointer to the FT_INFO structure, may be modified if alternate font is added
+ \param efsp Pointer to a Pointer to the original FNT_SPECS struct. On return contains the FNT_SPECS corresponding to the glyph_index..
+ \param wc Current character (32 bit int)
+*/
+int TR_find_alternate_font(FT_INFO *fti, FNT_SPECS **efsp, uint32_t wc){
+ int glyph_index=0; /* this is the unknown character glyph */
+ uint32_t i;
+ FcCharSet *cs;
+ FcResult result = FcResultMatch;
+ FcPattern *pattern, *fpat;
+ char *filename;
+ char *fontname;
+ char *newfontspec;
+ int fi_idx;
+ FNT_SPECS *fsp,*fsp2;
+ if(!fti || !efsp || !*efsp)return(0);
+ fsp = *efsp;
+ for(i=0;i<fsp->used;i++){ /* first check in alts */
+ fsp2 = &fti->fonts[fsp->alts[i].fi_idx]; /* these are in order of descending previous usage */
+ glyph_index = FT_Get_Char_Index( fsp2->face, wc); /* we have the face, might as well check that directly */
+ if (glyph_index){ /* found a glyph for the character in this font */
+ (void) fsp_alts_weight(fsp, i);
+ *efsp = fsp2;
+ return(glyph_index);
+ }
+ }
+
+ /* it was not in alts, now go through fontset and see if it is in there */
+ for(i=1; i< (unsigned int) fsp->fontset->nfont;i++){ /* already know the primary does not have this character */
+ result = FcPatternGetCharSet(fsp->fontset->fonts[i], FC_CHARSET, 0, &cs);
+ if(result != FcResultMatch) return(0); /* some terrible problem, this should never happen */
+ if (FcCharSetHasChar(cs, wc)){ /* found a glyph for the character in this font */
+ glyph_index = i;
+
+ /* Do a lot of work to find the filename corresponding to the fontset entry.
+ None of these should ever fail, but if one does, return 0
+ */
+ if(
+ !(pattern = FcNameParse((const FcChar8 *)&(fsp->fontspec))) ||
+ !FcConfigSubstitute(NULL, pattern, FcMatchPattern)
+ )return(0);
+ FcDefaultSubstitute(pattern);
+ if(
+ !(fpat = FcFontRenderPrepare(NULL, pattern, fsp->fontset->fonts[i])) ||
+ (FcPatternGetString( fpat, FC_FILE, 0, (FcChar8 **)&filename) != FcResultMatch) ||
+ (FcPatternGetString( fsp->fontset->fonts[i], FC_FULLNAME, 0, (FcChar8 **)&fontname) != FcResultMatch)
+ )return(0);
+
+ /* find the font (added from an unrelated fontset, for instance) or insert it as new */
+ fi_idx = ftinfo_find_loaded_by_src(fti, (uint8_t *) filename);
+ if(fi_idx < 0){
+ newfontspec = TR_reconstruct_fontspec((char *) fsp->fontspec, fontname);
+ fi_idx = ftinfo_load_fontname(fti, newfontspec);
+ free(newfontspec);
+ if(fi_idx < 0)return(0); /* This could happen if we run out of memory*/
+ }
+
+ /* add the new font index to the alts list on the (current) fsp. */
+ (void) fsp_alts_insert(fsp, fi_idx);
+
+ /* release FC's own memory related to this call that does not need to be kept around so that face will work */
+ FcPatternDestroy(pattern);
+
+ *efsp = &(fti->fonts[fi_idx]);
+ return(glyph_index);
+ }
+ }
+
+ return(0);
+}
+
+/**
+ \brief Get the advance for the 32 bit character
+
+ \return Returns -1 on error, or advance in units of 1/64th of a Point.
+ \param fti pointer to the FT_INFO structure, may be modified if alternate font is required
+ \param fsp Pointer to FNT_SPECS struct.
+ \param wc Current character (32 bit int)
+ \param pc Previous character
+ \param load_flags Controls internal advance:
+ FT_LOAD_NO_SCALE, internal advance is in 1/64th of a point. (kerning values are still scaled)
+ FT_LOAD_TARGET_NORMAL internal advance is in 1/64th of a point. The scale
+ factor seems to be (Font Size in points)*(DPI)/(32.0 pnts)*(72 dpi).
+ \param kern_mode FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED. Set to match calling application.
+ \param ymin If the pointer is defined, the value is adjusted if ymin of wc character is less than the current value.
+ \param ymax If the pointer is defined, the value is adjusted if ymin of wc character is more than the current value.
+*/
+int TR_getadvance(FT_INFO *fti, FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int load_flags, int kern_mode, int *ymin, int *ymax){
+ FT_Glyph glyph;
+ int glyph_index;
+ int advance=-1;
+ FT_BBox bbox;
+
+ if(is_mn_unicode(wc))return(0); /* no advance on Unicode Mn characters */
+
+ glyph_index = FT_Get_Char_Index( fsp->face, wc);
+ if(!glyph_index){ /* not in primary font, check alternates */
+ glyph_index = TR_find_alternate_font(fti, &fsp, wc);
+ }
+ if(glyph_index){
+ if (!FT_Load_Glyph( fsp->face, glyph_index, load_flags )){
+ if ( !FT_Get_Glyph( fsp->face->glyph, &glyph ) ) {
+ advance = fsp->face->glyph->advance.x;
+ FT_Glyph_Get_CBox( glyph, FT_GLYPH_BBOX_UNSCALED, &bbox );
+ if(ymin && (bbox.yMin < *ymin))*ymin=bbox.yMin;
+ if(ymax && (bbox.yMax > *ymax))*ymax=bbox.yMax;
+ if(pc)advance += TR_getkern2(fsp, wc, pc, kern_mode);
+ FT_Done_Glyph(glyph);
+ }
+ }
+ }
+ /* If there was no way to determine the width, this returns the error value */
+ return(advance);
+}
+
+/**
+ \brief Get the kerning for a pair of 32 bit characters
+ \return Returns 0 on error, or kerning value (which may be 0) for the pair in units of 1/64th of a point.
+ \param fsp Pointer to FNT_SPECS struct.
+ \param wc Current character (32 bit int)
+ \param pc Previous character
+ \param kern_mode FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED. Set to match calling application.
+*/
+int TR_getkern2(FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int kern_mode){
+ int this_glyph_index;
+ int prev_glyph_index;
+ int kern=0;
+ FT_Vector akerning;
+
+ this_glyph_index = FT_Get_Char_Index( fsp->face, wc);
+ prev_glyph_index = FT_Get_Char_Index( fsp->face, pc);
+ if(!FT_Get_Kerning( fsp->face,
+ prev_glyph_index,
+ this_glyph_index,
+ kern_mode,
+ &akerning )){
+ kern = akerning.x; /* Is sign correct? */
+ }
+ return(kern);
+}
+
+/**
+ \brief Get the kerning for a pair of 32 bit characters, where one is the last character in the previous text block, and the other is the first in the current text block.
+ \return Returns 0 on error, or kerning value (which may be 0) for the pair in units of 1/64th of a point.
+ \param fsp Pointer to FNT_SPECS struct.
+ \param tsp current text object
+ \param ptsp previous text object
+ \param kern_mode FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED. Set to match calling application.
+*/
+int TR_kern_gap(FNT_SPECS *fsp, TCHUNK_SPECS *tsp, TCHUNK_SPECS *ptsp, int kern_mode){
+ int kern=0;
+ uint32_t *text32=NULL;
+ uint32_t *ptxt32=NULL;
+ size_t tlen,plen;
+ while(ptsp && tsp){
+ text32 = U_Utf8ToUtf32le((char *) tsp->string, 0, &tlen);
+ if(!text32){ // LATIN1 encoded >128 are generally not valid UTF, so the first will fail
+ text32 = U_Latin1ToUtf32le((char *) tsp->string,0, &tlen);
+ if(!text32)break;
+ }
+ ptxt32 = U_Utf8ToUtf32le((char *) ptsp->string,0,&plen);
+ if(!ptxt32){ // LATIN1 encoded >128 are generally not valid UTF, so the first will fail
+ ptxt32 = U_Latin1ToUtf32le((char *) ptsp->string,0, &plen);
+ if(!ptxt32)break;
+ }
+ kern = TR_getkern2(fsp, *text32, ptxt32[plen-1], kern_mode);
+ break;
+ }
+ if(text32)free(text32);
+ if(ptxt32)free(ptxt32);
+ return(kern);
+}
+
+
+
+
+/**
+ \brief Find baseline on Y axis of a complex.
+ If the complex is a TR_TEXT or TR_LINE find its baseline.
+ If the complex is TR_PARA_[UCLR]J find the baseline of the last line.
+ If there are multiple text elements in a TR_LINE, the baseline is that of the
+ element that uses the largest font. This will definitely give the wrong
+ result if that line starts with a super or subscript that is full font size, but
+ they are usually smaller.
+ \return Returns 0 if it cannot determine a baseline, else returns the baseline Y coordinate.
+ \param tri pointer to the TR_INFO structure holding all TR data
+ \param src index of the current complex
+ \param ymax If the pointer is defined, the value is adjusted if ymax of current complex is more than the current value.
+ \param ymin If the pointer is defined, the value is adjusted if ymin of current complex is less than the current value.
+*/
+double TR_baseline(TR_INFO *tri, int src, double *ymax, double *ymin){
+ double baseline=0;
+ double tmp=0.0;
+ double yheight;
+ int last;
+ int i;
+ int trec;
+ CX_INFO *cxi=tri->cxi;
+ BR_INFO *bri=tri->bri;
+ TP_INFO *tpi=tri->tpi;
+ FT_INFO *fti=tri->fti;
+ FNT_SPECS *fsp;
+ last = cxi->cx[src].kids.used - 1;
+ switch (cxi->cx[src].type){
+ case TR_TEXT:
+ trec = cxi->cx[src].kids.members[0]; /* for this complex type there is only ever one member */
+ baseline = bri->rects[trec].yll - tpi->chunks[trec].boff;
+ fsp = &(fti->fonts[tpi->chunks[trec].fi_idx]);
+ yheight = fsp->face->bbox.yMax - fsp->face->bbox.yMin;
+ if(ymax){
+ tmp = tpi->chunks[trec].fs * ((double)fsp->face->bbox.yMax/yheight);
+ if(*ymax <= tmp)*ymax = tmp;
+ }
+ else if(ymin){
+ tmp = tpi->chunks[trec].fs * ((double)-fsp->face->bbox.yMin/yheight); /* yMin in face is negative */
+ if(*ymin <= tmp)*ymin = tmp;
+ }
+ break;
+ case TR_LINE:
+ for(i=last;i>=0;i--){ /* here last is the count of text objects in the complex */
+ trec = cxi->cx[src].kids.members[i];
+ fsp = &(fti->fonts[tpi->chunks[trec].fi_idx]);
+ yheight = fsp->face->bbox.yMax - fsp->face->bbox.yMin;
+ if(ymax){
+ tmp = tpi->chunks[trec].fs * (((double)fsp->face->bbox.yMax)/yheight);
+ if(*ymax <= tmp){
+ *ymax = tmp;
+ baseline = bri->rects[trec].yll - tpi->chunks[trec].boff;
+ }
+ }
+ else if(ymin){
+ tmp = tpi->chunks[trec].fs * (((double)-fsp->face->bbox.yMin)/yheight); /* yMin in face is negative */
+ if(*ymin <= tmp){
+ *ymin = tmp;
+ baseline = bri->rects[trec].yll - tpi->chunks[trec].boff;
+ }
+ }
+ }
+ break;
+ case TR_PARA_UJ:
+ case TR_PARA_LJ:
+ case TR_PARA_CJ:
+ case TR_PARA_RJ:
+ trec = cxi->cx[src].kids.members[last];
+ baseline = TR_baseline(tri, trec, ymax, ymin);
+ break;
+ }
+ return(baseline);
+}
+
+/**
+ \brief Check or set vertical advance on the growing complex relative to the current complex.
+ Vadvance is a multiplicative factor like 1.25.
+ The distance between successive baselines is vadvance * max(font_size), where the maximum
+ is over all text elements in src.
+ The growing complex is always the last one in the CX_INFO section of the TR_INFO structure.
+ If an existing vadvance does not match the one which would be required to fit the next complex
+ to add to the growing one, it terminates a growing complex. (Ie, starts a new paragraph.)
+ Find baseline on Y axis of a complex.
+ If the complex is a TR_TEXT or TR_LINE find its baseline.
+ If the complex is TR_PARA+* find the baseline of the last line.
+ If there are multiple text elements in a TR_LINE, the baseline is that of the
+ element that uses the largest font. This will definitely give the wrong
+ result if that line starts with a super or subscript that is full font size, but
+ they are usually smaller.
+ \return Returns 0 on success, !0 on failure.
+ \param tri pointer to the TR_INFO structure holding all TR data
+ \param src index of the current complex, to be added to the growing complex.
+ This lets the value of "src - lines" determine the weight to give to each new vadvance value
+ as it is merged into the running weighted average. This improves the accuracy of the vertical advance,
+ since there can be some noise introduced when lines have different maximum font sizes.
+ \param lines index of the first text block that was added to the growing complex.
+*/
+int TR_check_set_vadvance(TR_INFO *tri, int src, int lines){
+ int status = 0;
+ CX_INFO *cxi = tri->cxi;
+ TP_INFO *tpi = tri->tpi;
+ double ymax = DBL_MIN;
+ double ymin = DBL_MIN;
+ double prevbase;
+ double thisbase;
+ double weight;
+ int trec;
+ double newV;
+ int dst;
+
+ dst = cxi->used-1; /* complex being grown */
+
+ prevbase = TR_baseline(tri, dst, NULL, &ymin);
+ thisbase = TR_baseline(tri, src, &ymax, NULL);
+ newV = (thisbase - prevbase)/(ymax + ymin);
+ trec = cxi->cx[dst].kids.members[0]; /* complex whose first text record holds vadvance for this complex */
+ trec = cxi->cx[trec].kids.members[0]; /* text record that halds vadvance for this complex */
+ if(tpi->chunks[trec].vadvance){
+ /* already set on the first text (only place it is stored.)
+ See if the line to be added is compatible.
+ All text fields in a complex have the same advance, so just set/check the first one.
+ vadvance must be within 1% or do not add a new line */
+ if(fabs(1.0 - (tpi->chunks[trec].vadvance/newV)) > 0.01){
+ status = 1;
+ }
+ else { /* recalculate the weighted vadvance */
+ weight = (1.0 / (double) (src - lines));
+ tpi->chunks[trec].vadvance = tpi->chunks[trec].vadvance*(1.0-weight) + newV*weight;
+ }
+ }
+ else { /* only happens when src = lines + 1*/
+ tpi->chunks[trec].vadvance = newV;
+ }
+ return(status);
+}
+
+
+/**
+ \brief Initialize an FT_INFO structure. Sets up a freetype library to use in this context.
+ \returns a pointer to the FT_INFO structure created, or NULL on error.
+*/
+FT_INFO *ftinfo_init(void){
+ FT_INFO *fti = NULL;
+ if(FcInit()){
+ fti = (FT_INFO *)calloc(1,sizeof(FT_INFO));
+ if(fti){
+ if(!FT_Init_FreeType( &(fti->library))){
+ fti->space=0;
+ fti->used=0;
+
+ if(ftinfo_make_insertable(fti)){
+ FT_Done_FreeType(fti->library);
+ free(fti);
+ fti=NULL;
+ }
+ }
+ else {
+ free(fti);
+ fti=NULL;
+ }
+ }
+ if(!fti)FcFini();
+ }
+ return(fti);
+}
+
+/**
+ \brief Make an FT_INFO structure insertable. Adds storage as needed.
+ \param fti pointer to the FT_INFO structure
+ \returns 0 on success, !0 on error.
+*/
+int ftinfo_make_insertable(FT_INFO *fti){
+ int status=0;
+ FNT_SPECS *tmp;
+ if(!fti)return(2);
+ if(fti->used >= fti->space){
+ fti->space += ALLOCINFO_CHUNK;
+ tmp = (FNT_SPECS *) realloc(fti->fonts, fti->space * sizeof(FNT_SPECS) );
+ if(tmp){
+ fti->fonts = tmp;
+ memset(&fti->fonts[fti->used],0,(fti->space - fti->used)*sizeof(FNT_SPECS));
+ }
+ else {
+ status=1;
+ }
+ }
+ return(status);
+}
+
+
+/**
+ \brief Insert a copy of a FNT_SPECS structure into the FT_INFO structure.
+ \param fti pointer to the FT_INFO structure.
+ \param fsp pointer to the FNT_SPECS structure.
+ \returns 0 on success, !0 on error.
+*/
+int ftinfo_insert(FT_INFO *fti, FNT_SPECS *fsp){
+ int status=1;
+ if(!fti)return(2);
+ if(!fsp)return(3);
+ if(!(status = ftinfo_make_insertable(fti))){
+ memcpy(&(fti->fonts[fti->used]),fsp,sizeof(FNT_SPECS));
+ fti->used++;
+ }
+ return(status);
+}
+
+
+
+/**
+ \brief Release an FT_INFO structure. Release all associated memory.
+ Use like: fi_ptr = ftinfo_release(fi_ptr)
+ \param fti pointer to the FT_INFO structure.
+ \returns NULL.
+*/
+FT_INFO *ftinfo_release(FT_INFO *fti){
+ (void) ftinfo_clear(fti);
+ FcFini(); /* shut down FontConfig, release memory, patterns must have already been released or boom! */
+ return NULL;
+}
+
+
+/**
+ \brief Clear an FT_INFO structure. Release all Freetype memory but does not release Fontconfig.
+ This would be called in preference to ftinfo_release() if some other part of the program needed
+ to continue using Fontconfig.
+ Use like: fi_ptr = ftinfo_clear(fi_ptr)
+ \param fti pointer to the FT_INFO structure.
+ \returns NULL.
+*/
+FT_INFO *ftinfo_clear(FT_INFO *fti){
+ uint32_t i;
+ FNT_SPECS *fsp;
+ if(fti){
+ for(i=0;i<fti->used;i++){
+ fsp = &(fti->fonts[i]);
+ FT_Done_Face(fsp->face); /* release memory for face controlled by FreeType */
+ free(fsp->file); /* release memory holding copies of paths */
+ free(fsp->fontspec); /* release memory holding copies of font names */
+ FcPatternDestroy(fsp->fpat); /* release memory for FontConfig fpats */
+ FcFontSetDestroy(fsp->fontset);
+ if(fsp->alts){ free(fsp->alts); }
+ }
+ free(fti->fonts);
+ FT_Done_FreeType(fti->library); /* release all other FreeType memory */
+ free(fti);
+ }
+ return NULL;
+}
+
+
+/**
+ \brief Find the loaded font matching fontspec
+ \returns index of font on success, -1 if not found
+ \param tri pointer to the TR_INFO structure.
+ \param fontspec UTF-8 description of the font, as constructed in trinfo_load_fontname
+*/
+
+int ftinfo_find_loaded_by_spec(const FT_INFO *fti, const uint8_t *fontspec){
+ uint32_t i;
+ int status = -1;
+ /* If it is already loaded, do not load it again */
+ for(i=0;i<fti->used;i++){
+ if(0==strcmp((char *) fti->fonts[i].fontspec, (char *)fontspec)){
+ status=i;
+ break;
+ }
+ }
+ return(status);
+}
+
+/**
+ \brief Find the loaded font matching the source file
+ \returns index of font on success, -1 if not found
+ \param tri pointer to the TR_INFO structure.
+ \param filename UTF-8 file name for the font
+*/
+
+int ftinfo_find_loaded_by_src(const FT_INFO *fti, const uint8_t *filename){
+ uint32_t i;
+ int status = -1;
+ /* If it is already loaded, do not load it again */
+ for(i=0;i<fti->used;i++){
+ if(0==strcmp((char *) fti->fonts[i].file, (char *) filename)){
+ status=i;
+ break;
+ }
+ }
+ return(status);
+}
+
+
+/**
+ \brief Load a (new) font by name into a TR_INFO structure or find it if it is already loaded
+ \returns fi_idx of inserted (or found) font on success, <0 on error.
+ \param fti pointer to the FT_INFO structure.
+ \param fontname UTF-8 font name
+ \param fontspec UTF-8 font specification used for query string.
+*/
+
+int ftinfo_load_fontname(FT_INFO *fti, const char *fontspec){
+ FcPattern *pattern = NULL;
+ FcPattern *fpat = NULL;
+ FcFontSet *fontset = NULL;
+ FcResult result = FcResultMatch;
+ char *filename;
+ double fd;
+ FNT_SPECS *fsp;
+ int status;
+ int fi_idx;
+
+ if(!fti)return(-1);
+
+ /* If it is already loaded, do not load it again */
+ status = ftinfo_find_loaded_by_spec(fti, (uint8_t *) fontspec);
+ if(status >= 0){ return(status); }
+ status = 0; /* was -1, reset to 0 */
+
+ ftinfo_make_insertable(fti);
+ fi_idx = fti->used;
+
+ pattern = FcNameParse((const FcChar8 *)fontspec);
+ while(1) { /* this is NOT a loop, it uses breaks to avoid gotos and deep nesting */
+ if(!(pattern)){ status = -2; break; }
+ if(!FcConfigSubstitute(NULL, pattern, FcMatchPattern)){ status = -3; break; };
+ FcDefaultSubstitute(pattern);
+ /* get a fontset, trimmed to only those with new glyphs as needed, so that missing glyph's may be handled */
+ if(!(fontset = FcFontSort (NULL,pattern, FcTrue, NULL, &result)) || (result != FcResultMatch)){ status = -4; break; }
+ if(!(fpat = FcFontRenderPrepare(NULL, pattern, fontset->fonts[0]))){ status = -405; break; }
+ if(FcPatternGetString( fpat, FC_FILE, 0, (FcChar8 **)&filename) != FcResultMatch){ status = -5; break; }
+ if(FcPatternGetDouble( fpat, FC_SIZE, 0, &fd) != FcResultMatch){ status = -6; break; }
+
+ /* copy these into memory for external use */
+ fsp = &(fti->fonts[fti->used]);
+ fsp->fontset = fontset;
+ fsp->alts = NULL; /* Initially no links to alternate fonts */
+ fsp->space = 0;
+ fsp->file = (uint8_t *) U_strdup((char *) filename);
+ fsp->fontspec = (uint8_t *) U_strdup((char *) fontspec);
+ fsp->fpat = fpat;
+ fsp->fsize = fd;
+ break;
+ }
+ /* release FC's own memory related to this call that does not need to be kept around so that face will work */
+ if(pattern)FcPatternDestroy(pattern); /* done with this memory */
+ if(status<0){
+ if(fontset)FcFontSetDestroy(fontset);
+ if(fpat)FcPatternDestroy(fpat);
+ return(status);
+ }
+
+ /* get the current face */
+ if(FT_New_Face( fti->library, (const char *) fsp->file, 0, &(fsp->face) )){ return(-8); }
+
+ if(FT_Set_Char_Size(
+ fsp->face, /* handle to face object */
+ 0, /* char_width in 1/64th of points */
+ fd*64, /* char_height in 1/64th of points */
+ 72, /* horizontal device resolution, DPI */
+ 72) /* vebrical device resolution, DPI */
+ ){ return(-9); }
+
+ /* The space advance is needed in various places. Get it now, and get it in the font units,
+ so that it can be scaled later with the text size */
+ status = TR_getadvance(fti, fsp,' ',0,FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP, FT_KERNING_UNSCALED, NULL, NULL);
+ if(status < 0)return(-7);
+ fsp->spcadv = ((double) status)/(64.0);
+
+ fti->used++;
+
+/*
+ char *fs;
+ int fb;
+ if(FcPatternGetBool( fpat, FC_OUTLINE, 0, &fb)== FcResultMatch){ printf("outline: %d\n",fb);fflush(stdout); }
+ if(FcPatternGetBool( fpat, FC_SCALABLE, 0, &fb)== FcResultMatch){ printf("scalable: %d\n",fb);fflush(stdout); }
+ if(FcPatternGetDouble( fpat, FC_DPI, 0, &fd)== FcResultMatch){ printf("DPI: %f\n",fd);fflush(stdout); }
+ if(FcPatternGetInteger( fpat, FC_FONTVERSION, 0, &fb)== FcResultMatch){ printf("fontversion: %d\n",fb);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_FULLNAME , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FULLNAME : %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_FAMILY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FAMILY : %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_STYLE , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("STYLE : %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_FOUNDRY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FOUNDRY : %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_FAMILYLANG , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FAMILYLANG : %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_STYLELANG , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("STYLELANG : %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_FULLNAMELANG, 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FULLNAMELANG: %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_CAPABILITY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("CAPABILITY : %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_FONTFORMAT , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FONTFORMAT : %s\n",fs);fflush(stdout); }
+*/
+
+ return(fi_idx);
+}
+
+/**
+ \brief Dump the contents of the TR_INFO structure to stdout. For debugging purposes,not used in production code.
+ \param tri pointer to the TR_INFO structure.
+*/
+void ftinfo_dump(const FT_INFO *fti){
+ uint32_t i,j;
+ FNT_SPECS *fsp;
+ printf("fti space: %d\n",fti->space);
+ printf("fti used: %d\n",fti->used);
+ for(i=0; i< fti->used; i++){
+ fsp = &(fti->fonts[i]);
+ printf("fti font: %6d space: %6d used: %6d spcadv %8f fsize %8f \n",i,fsp->space,fsp->used,fsp->spcadv,fsp->fsize);
+ printf(" file: %s\n",fsp->file);
+ printf(" fspc: %s\n",fsp->fontspec);
+ for(j=0;j<fsp->used;j++){
+ printf(" alts: %6d fi_idx: %6d wgt: %6d\n",j,fsp->alts[j].fi_idx,fsp->alts[j].weight);
+ }
+ }
+
+}
+
+/**
+ \brief Make the FNT_SPECS alts structure insertable. Adds storage as needed.
+ \param fti pointer to the FT_INFO structure
+ \returns 0 on success, !0 on error.
+*/
+int fsp_alts_make_insertable(FNT_SPECS *fsp){
+ int status=0;
+ ALT_SPECS *tmp;
+ if(!fsp)return(2);
+ if(fsp->used >= fsp->space){
+ fsp->space += ALLOCINFO_CHUNK;
+ tmp = (ALT_SPECS *) realloc(fsp->alts, fsp->space * sizeof(ALT_SPECS) );
+ if(tmp){
+ fsp->alts = tmp;
+ memset(&fsp->alts[fsp->used],0,(fsp->space - fsp->used)*sizeof(ALT_SPECS));
+ }
+ else {
+ status=1;
+ }
+ }
+ return(status);
+}
+
+
+/**
+ \brief Insert a new ALT_SPECS into the FNT_SPECS alts list.
+ \param fsp pointer to the FNT_SPECS structure.
+ \param fi_idx font index to add to the alts list
+ \returns 0 on success, !0 on error.
+*/
+int fsp_alts_insert(FNT_SPECS *fsp, uint32_t fi_idx){
+ int status=1;
+ ALT_SPECS alt;
+ if(!fsp)return(3);
+ alt.fi_idx = fi_idx;
+ alt.weight = 1; /* new ones start with this weight, it can only go up */
+ if(!(status = fsp_alts_make_insertable(fsp))){
+ fsp->alts[fsp->used] = alt;
+ fsp->used++;
+ }
+ return(status);
+}
+
+/**
+ \brief Increment the weight of an alts entry by 1, readjust order if necessary
+ \param fsp pointer to the FNT_SPECS structure.
+ \param idx index of the alts entry to increment
+ \returns 0 on success, !0 on error.
+*/
+int fsp_alts_weight(FNT_SPECS *fsp, uint32_t a_idx){
+ uint32_t i;
+ ALT_SPECS alt;
+ if(!fsp)return(1);
+ if(!fsp->used)return(2);
+ if(a_idx >= fsp->used)return(3);
+ /* If a counter hits the limit divide all counts in half. */
+ if(fsp->alts[a_idx].weight == UINT32_MAX){
+ for(i=0; i<fsp->used; i++){ fsp->alts[i].weight /= 2; }
+ }
+ fsp->alts[a_idx].weight++;
+ for(i=a_idx; i>0; i--){
+ if(fsp->alts[i-1].weight >= fsp->alts[a_idx].weight)break;
+ alt = fsp->alts[i-1];
+ fsp->alts[i-1] = fsp->alts[a_idx];
+ fsp->alts[a_idx] = alt;
+ }
+ return(0);
+}
+
+
+
+/**
+ \brief Make a CHILD_SPECS structure insertable. Adds storage as needed.
+ \param csp pointer to the CHILD_SPECS structure
+ \returns 0 on success, !0 on error.
+*/
+int csp_make_insertable(CHILD_SPECS *csp){
+ int status=0;
+ int *tmp;
+ if(!csp)return(2);
+ if(csp->used >= csp->space){
+ csp->space += ALLOCINFO_CHUNK;
+ tmp = (int *) realloc(csp->members, csp->space * sizeof(int) );
+ if(tmp){
+ csp->members = tmp;
+ memset(&csp->members[csp->used],0,(csp->space - csp->used)*sizeof(int));
+ }
+ else {
+ status=1;
+ }
+ }
+ return(status);
+}
+
+/**
+ \brief Add a member to a CHILD_SPECS structure. (Member is an index for either a text object or a complex.)
+ \param dst pointer to the CHILD_SPECS structure.
+ \param src index of the member.
+ \returns 0 on success, !0 on error.
+*/
+int csp_insert(CHILD_SPECS *dst, int src){
+ int status=1;
+ if(!dst)return(2);
+ if(!(status=csp_make_insertable(dst))){
+ dst->members[dst->used]=src;
+ dst->used++;
+ }
+ return(status);
+}
+
+/**
+ \brief Append all the members of one CHILD_SPECS structure to another CHILD_SPECS structure.
+ Member is an index for either a text object or a complex.
+ The donor is not modified.
+ \param dst pointer to the recipient CHILD_SPECS structure.
+ \param src pointer to the donor CHILD_SPECS structure.
+ \returns 0 on success, !0 on error.
+*/
+int csp_merge(CHILD_SPECS *dst, CHILD_SPECS *src){
+ uint32_t i;
+ int status=1;
+ if(!dst)return(2);
+ if(!src)return(3);
+ for(i=0;i<src->used;i++){
+ status = csp_insert(dst, src->members[i]);
+ if(status)break;
+ }
+ return(status);
+}
+
+/**
+ \brief Release a CHILD_SPECS structure. Release all associated memory.
+ \param csp pointer to the CHILD_SPECS structure.
+ \returns NULL.
+*/
+void csp_release(CHILD_SPECS *csp){
+ if(csp){
+ free(csp->members);
+ csp->space = 0;
+ csp->used = 0;
+ }
+}
+
+/**
+ \brief Clear a CHILD_SPECS structure, making all allocated slots usable. Does not release associated memory.
+ \param csp pointer to the CHILD_SPECS structure.
+ \returns NULL.
+*/
+void csp_clear(CHILD_SPECS *csp){
+ csp->used = 0;
+}
+
+
+/**
+ \brief Initialize an CX_INFO structure. Holds complexes (multiple text objects in known positions and order.)
+ \returns a pointer to the CX_INFO structure created, or NULL on error.
+*/
+CX_INFO *cxinfo_init(void){
+ CX_INFO *cxi = NULL;
+ cxi = (CX_INFO *)calloc(1,sizeof(CX_INFO));
+ if(cxi){
+ if(cxinfo_make_insertable(cxi)){
+ free(cxi);
+ cxi=NULL;
+ }
+ }
+ return(cxi);
+}
+
+/**
+ \brief Make a CX_INFO structure insertable. Adds storage as needed.
+ \returns 0 on success, !0 on error.
+ \param cxi pointer to the CX_INFO structure
+*/
+int cxinfo_make_insertable(CX_INFO *cxi){
+ int status=0;
+ CX_SPECS *tmp;
+ if(cxi->used >= cxi->space){
+ cxi->space += ALLOCINFO_CHUNK;
+ tmp = (CX_SPECS *) realloc(cxi->cx, cxi->space * sizeof(CX_SPECS) );
+ if(tmp){
+ cxi->cx = tmp;
+ memset(&cxi->cx[cxi->used],0,(cxi->space - cxi->used)*sizeof(CX_SPECS));
+ }
+ else {
+ status=1;
+ }
+ }
+ return(status);
+}
+
+/**
+ \brief Insert a complex into the CX_INFO structure. (Insert may be either TR_TEXT or TR_LINE.)
+ \returns 0 on success, !0 on error.
+ \param cxi pointer to the CX_INFO structure (complexes).
+ \param src index of the complex to insert.
+ \param src_rt_tidx index of the bounding rectangle
+ \param type TR_TEXT (index is for tpi->chunks[]) or TR_LINE (index is for cxi->kids[])
+*/
+int cxinfo_insert(CX_INFO *cxi, int src, int src_rt_tidx, enum tr_classes type){
+ int status=1;
+ if(!cxi)return(2);
+ if(!(status=cxinfo_make_insertable(cxi))){
+ cxi->cx[cxi->used].rt_cidx = src_rt_tidx;
+ cxi->cx[cxi->used].type = type;
+ status = csp_insert(&(cxi->cx[cxi->used].kids), src);
+ cxi->used++;
+ }
+ return(status);
+}
+
+/**
+ \brief Append a complex to the CX_INFO structure and give it a type.
+ \param cxi pointer to the CX_INFO structure (complexes).
+ \param src index of the complex to append.
+ \param type TR_LINE (src is an index for tpi->chunks[]) or TR_PARA (src is an index for cxi->kids[]).
+ \returns 0 on success, !0 on error.
+*/
+int cxinfo_append(CX_INFO *cxi, int src, enum tr_classes type){
+ int status=1;
+ if(!cxi)return(2);
+ if(!(status=cxinfo_make_insertable(cxi))){
+ cxi->cx[cxi->used-1].type = type;
+ status = csp_insert(&(cxi->cx[cxi->used-1].kids), src);
+ }
+ return(status);
+}
+
+
+/**
+ \brief Merge a complex dst with N members (N>=1) by adding a second complex src, and change the type.
+ \param cxi pointer to the CX_INFO structure (complexes).
+ \param dst index of the complex to expand.
+ \param src index of the donor complex (which is not modified).
+ \param type TR_LINE (src is an index for tpi->chunks[]) or TR_PARA (src is an index for cxi->kids[]).
+ \returns 0 on success, !0 on error.
+*/
+int cxinfo_merge(CX_INFO *cxi, int dst, int src, enum tr_classes type){
+ int status =1;
+ if(!cxi)return(2);
+ if(!cxi->used)return(3);
+ if(dst < 0 || dst >= (int) cxi->used)return(4);
+ if(src < 0)return(5);
+ cxi->cx[dst].type = type;
+ status = csp_merge(&(cxi->cx[dst].kids), &(cxi->cx[src].kids));
+ return(status);
+}
+
+/**
+ \brief Trim the last complex from thelist of complexes.
+ \param cxi pointer to the CX_INFO structure (complexes).
+ \returns 0 on success, !0 on error.
+*/
+int cxinfo_trim(CX_INFO *cxi){
+ int status = 0;
+ int last ;
+ if(!cxi)return(1);
+ if(!cxi->used)return(2);
+ last = cxi->used - 1;
+ csp_clear(&(cxi->cx[last].kids));
+ cxi->used--;
+ return(status);
+}
+
+
+/**
+ \brief Dump the contents of the TR_INFO structure to stdout. For debugging purposes,not used in production code.
+ \param tri pointer to the TR_INFO structure.
+*/
+void cxinfo_dump(const TR_INFO *tri){
+ uint32_t i,j,k;
+ CX_INFO *cxi = tri->cxi;
+ BR_INFO *bri = tri->bri;
+ TP_INFO *tpi = tri->tpi;
+ BRECT_SPECS *bsp;
+ CX_SPECS *csp;
+ if(cxi){
+ printf("cxi space: %d\n",cxi->space);
+ printf("cxi used: %d\n",cxi->used);
+ printf("cxi phase1: %d\n",cxi->phase1);
+ printf("cxi lines: %d\n",cxi->lines);
+ printf("cxi paras: %d\n",cxi->paras);
+ printf("cxi xy: %f , %f\n",tri->x,tri->y);
+
+ for(i=0;i<cxi->used;i++){
+ csp = &(cxi->cx[i]);
+ bsp = &(bri->rects[csp->rt_cidx]);
+ printf("cxi cx[%d] type:%d rt_tidx:%d kids_used:%d kids_space:%d\n",i, csp->type, csp->rt_cidx, csp->kids.used, csp->kids.space);
+ printf("cxi cx[%d] br (LL,UR) (%f,%f),(%f,%f)\n",i,bsp->xll,bsp->yll,bsp->xur,bsp->yur);
+ for(j=0;j<csp->kids.used;j++){
+ k = csp->kids.members[j];
+ bsp = &(bri->rects[k]);
+ if(csp->type == TR_TEXT || csp->type == TR_LINE){
+ printf("cxi cx[%d] member:%3d tp_idx:%3d ldir:%d rt_tidx:%3d br (LL,UR) (%8.3f,%8.3f),(%8.3f,%8.3f) xy (%8.3f,%8.3f) kern (%8.3f,%8.3f) text:<%s> decor:%5.5x\n",
+ i, j, k, tpi->chunks[k].ldir, tpi->chunks[k].rt_tidx,
+ bsp->xll,bsp->yll,bsp->xur,bsp->yur,
+ tpi->chunks[k].x, tpi->chunks[k].y,
+ tpi->chunks[k].xkern, tpi->chunks[k].ykern,
+ tpi->chunks[k].string, tpi->chunks[k].decoration );
+ }
+ else { /* TR_PARA_* */
+ printf("cxi cx[%d] member:%d cx_idx:%d\n",i, j, k);
+ }
+ }
+ }
+ }
+ return;
+}
+
+/**
+ \brief Release a CX_INFO structure. Release all associated memory.
+ use like: cxi = cxiinfo_release(cxi);
+ \param cxi pointer to the CX_INFO structure.
+ \returns NULL.
+*/
+CX_INFO *cxinfo_release(CX_INFO *cxi){
+ uint32_t i;
+ if(cxi){
+ for(i=0;i<cxi->used;i++){ csp_release(&cxi->cx[i].kids); }
+ free(cxi->cx);
+ free(cxi); /* release the overall cxinfo structure */
+ }
+ return NULL;
+}
+
+
+/**
+ \brief Initialize an TP_INFO structure. Holds text objects from which complexes are built.
+ \returns a pointer to the TP_INFO structure created, or NULL on error.
+*/
+TP_INFO *tpinfo_init(void){
+ TP_INFO *tpi = NULL;
+ tpi = (TP_INFO *)calloc(1,sizeof(TP_INFO));
+ if(tpi){
+ if(tpinfo_make_insertable(tpi)){
+ free(tpi);
+ tpi=NULL;
+ }
+ }
+ return(tpi);
+}
+
+
+/**
+ \brief Make a TP_INFO structure insertable. Adds storage as needed.
+ \returns 0 on success, !0 on error.
+ \param tpi pointer to the TP_INFO structure
+*/
+int tpinfo_make_insertable(TP_INFO *tpi){
+ int status=0;
+ TCHUNK_SPECS *tmp;
+ if(tpi->used >= tpi->space){
+ tpi->space += ALLOCINFO_CHUNK;
+ tmp = (TCHUNK_SPECS *) realloc(tpi->chunks, tpi->space * sizeof(TCHUNK_SPECS) );
+ if(tmp){
+ tpi->chunks = tmp;
+ memset(&tpi->chunks[tpi->used],0,(tpi->space - tpi->used)*sizeof(TCHUNK_SPECS));
+ }
+ else {
+ status=1;
+ }
+ }
+ return(status);
+}
+
+/**
+ \brief Insert a copy of a TCHUNK_SPECS structure into a TP_INFO structure. (Insert a text object.)
+ \returns 0 on success, !0 on error.
+ \param tpi pointer to the TP_INFO structure
+ \param tsp pointer to the TCHUNK_SPECS structure
+*/
+int tpinfo_insert(TP_INFO *tpi, const TCHUNK_SPECS *tsp){
+ int status=1;
+ TCHUNK_SPECS *ltsp;
+ if(!tpi)return(2);
+ if(!tsp)return(3);
+ if(!(status = tpinfo_make_insertable(tpi))){
+ ltsp = &(tpi->chunks[tpi->used]);
+ memcpy(ltsp,tsp,sizeof(TCHUNK_SPECS));
+ if(tsp->co)ltsp->condensed = 75; /* Narrow was set in the font name */
+ ltsp->xkern = ltsp->ykern = 0.0; /* kerning will be calculated from the derived layout */
+ tpi->used++;
+ }
+ return(status);
+}
+
+/**
+ \brief Release a TP_INFO structure. Release all associated memory.
+ use like: tpi = tpinfo_release(tpi);
+ \returns NULL.
+ \param tpi pointer to the TP_INFO structure.
+*/
+TP_INFO *tpinfo_release(TP_INFO *tpi){
+ uint32_t i;
+ if(tpi){
+ for(i=0;i<tpi->used;i++){
+ free(tpi->chunks[i].string); }
+ free(tpi->chunks); /* release the array */
+ free(tpi); /* release the overall tpinfo structure */
+ }
+ return NULL;
+}
+
+/**
+ \brief Initialize an BR_INFO structure. Holds bounding rectangles, for both text objects and complexes.
+ \returns a pointer to the BR_INFO structure created, or NULL on error.
+*/
+BR_INFO *brinfo_init(void){
+ BR_INFO *bri = NULL;
+ bri = (BR_INFO *)calloc(1,sizeof(BR_INFO));
+ if(bri){
+ if(brinfo_make_insertable(bri)){
+ free(bri);
+ bri=NULL;
+ }
+ }
+ return(bri);
+}
+
+/**
+ \brief Make a BR_INFO structure insertable. Adds storage as needed.
+ \returns 0 on success, !0 on error.
+ \param bri pointer to the BR_INFO structure
+*/
+int brinfo_make_insertable(BR_INFO *bri){
+ int status=0;
+ BRECT_SPECS *tmp;
+ if(!bri)return(2);
+ if(bri->used >= bri->space){
+ bri->space += ALLOCINFO_CHUNK;
+ tmp = (BRECT_SPECS *) realloc(bri->rects, bri->space * sizeof(BRECT_SPECS) );
+ if(tmp){ bri->rects = tmp; }
+ else { status = 1;}
+ }
+ return(status);
+}
+
+/**
+ \brief Insert a copy of a BRECT_SPEC structure into a BR_INFO structure. (Insert a bounding rectangle.)
+ \returns 0 on success, !0 on error.
+ \param bri pointer to the BR_INFO structure
+ \param element pointer to the BRECT_SPECS structure
+*/
+int brinfo_insert(BR_INFO *bri, const BRECT_SPECS *element){
+ int status=1;
+ if(!bri)return(2);
+ if(!(status=brinfo_make_insertable(bri))){
+ memcpy(&(bri->rects[bri->used]),element,sizeof(BRECT_SPECS));
+ bri->used++;
+ }
+ return(status);
+}
+
+/**
+ \brief Merge BRECT_SPEC element src into/with BRECT_SPEC element dst. src is unchanged. (Merge two bounding rectangles.)
+ \returns 0 on success, !0 on error.
+ \param bri pointer to the BR_INFO structure
+ \param dst index of the destination bounding rectangle.
+ \param src index of the source bounding rectangle.
+*/
+int brinfo_merge(BR_INFO *bri, int dst, int src){
+ if(!bri)return(1);
+ if(!bri->used)return(2);
+ if(dst<0 || dst >= (int) bri->used)return(3);
+ if(src<0 || src >= (int) bri->used)return(4);
+ bri->rects[dst].xll = TEREMIN(bri->rects[dst].xll, bri->rects[src].xll);
+ bri->rects[dst].yll = TEREMAX(bri->rects[dst].yll, bri->rects[src].yll); /* MAX because Y is positive DOWN */
+ bri->rects[dst].xur = TEREMAX(bri->rects[dst].xur, bri->rects[src].xur);
+ bri->rects[dst].yur = TEREMIN(bri->rects[dst].yur, bri->rects[src].yur); /* MIN because Y is positive DOWN */
+/*
+printf("bri_Merge into rect:%d (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n",dst,
+(bri->rects[dst].xll),
+(bri->rects[dst].yll),
+(bri->rects[dst].xur),
+(bri->rects[dst].yur),
+(bri->rects[src].xll),
+(bri->rects[src].yll),
+(bri->rects[src].xur),
+(bri->rects[src].yur));
+*/
+ return(0);
+}
+
+/**
+ \brief Check for an allowable overlap of two bounding rectangles.
+ Allowable overlap is any area overlap of src and dst bounding rectangles, after
+ they have been expanded (padded) by allowed edge expansions. (For instance, if
+ missing spaces must be accounted for.)
+ The method works backwards: look for all reasons they might not overlap,
+ if none are found, then the rectangles do overlap.
+ An overlap here does not count just a line or a point - area must be involved.
+ \returns 0 on success (overlap detected), 1 on no overlap, anything else is an error.
+ \param bri pointer to the BR_INFO structure
+ \param dst index of the destination bounding rectangle.
+ \param src index of the source bounding rectangle.
+ \param rp_dst Pointer to edge padding values for dst.
+ \param rp_src Pointer to edge padding values for src.
+*/
+int brinfo_overlap(const BR_INFO *bri, int dst, int src, RT_PAD *rp_dst, RT_PAD *rp_src){
+ int status;
+ BRECT_SPECS *br_dst;
+ BRECT_SPECS *br_src;
+ if(!bri || !rp_dst || !rp_src)return(2);
+ if(!bri->used)return(3);
+ if(dst<0 || dst>= (int) bri->used)return(4);
+ if(src<0 || src>= (int) bri->used)return(5);
+ br_dst=&bri->rects[dst];
+ br_src=&bri->rects[src];
+ if( /* Test all conditions that exclude overlap, if any are true, then no overlap */
+ ((br_dst->xur + rp_dst->right) < (br_src->xll - rp_src->left) ) || /* dst fully to the left */
+ ((br_dst->xll - rp_dst->left) > (br_src->xur + rp_src->right) ) || /* dst fully to the right */
+ ((br_dst->yur - rp_dst->up) > (br_src->yll + rp_src->down) ) || /* dst fully below (Y is positive DOWN) */
+ ((br_dst->yll + rp_dst->down) < (br_src->yur - rp_src->up) ) /* dst fully above (Y is positive DOWN) */
+ ){
+ status = 1;
+ }
+ else {
+ /* overlap not excluded, so it must occur.
+ Only accept overlaps that are mostly at one end or the other, not mostly top or bottom.
+ If the following condition is true then there is no more than a tiny bit of horizontal overlap of src
+ within dist, which suggests that the two pieces of text may be considered part of one line.
+ (For a vertical alphabet the same method could be used for up/down.) */
+ if(
+ (br_src->xll >= br_dst->xur - rp_dst->right) || /* src overlaps just a little on the right (L->R language) */
+ (br_src->xur <= br_dst->xll + rp_dst->left) /* src overlaps just a little on the left (R->L language) */
+ ){
+ status = 0;
+ }
+ else { /* Too much overlap, reject the overlap */
+ status = 1;
+ }
+ }
+/*
+printf("Overlap status:%d\nOverlap trects (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n",
+status,
+(br_dst->xll - rp_dst->left ),
+(br_dst->yll - rp_dst->down ),
+(br_dst->xur + rp_dst->right),
+(br_dst->yur + rp_dst->up ),
+(br_src->xll - rp_src->left ),
+(br_src->yll - rp_src->down ),
+(br_src->xur + rp_src->right),
+(br_src->yur + rp_src->up ));
+printf("Overlap brects (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n",
+(br_dst->xll),
+(br_dst->yll),
+(br_dst->xur),
+(br_dst->yur),
+(br_src->xll),
+(br_src->yll),
+(br_src->xur),
+(br_src->yur));
+printf("Overlap rprect (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n",
+(rp_dst->left),
+(rp_dst->down),
+(rp_dst->right),
+(rp_dst->up),
+(rp_src->left),
+(rp_src->down),
+(rp_src->right),
+(rp_src->up));
+*/
+ return(status);
+}
+
+/**
+ \brief Check for various sorts of invalid text elements upstream (language dir changes, draw order backwards from language direction)
+ \returns 0 on success (not upstream), 1 if upstream, anything else is an error.
+ \param bri pointer to the BR_INFO structure
+ \param dst index of the destination bounding rectangle.
+ \param src index of the source bounding rectangle.
+ \param ddir direction of dst
+ \param sdir direction of src
+*/
+
+int brinfo_upstream(BR_INFO *bri, int dst, int src, int ddir, int sdir){
+ int status=0;
+ BRECT_SPECS *br_dst;
+ BRECT_SPECS *br_src;
+ if(!bri)return(2);
+ if(!bri->used)return(3);
+ if(dst<0 || dst>= (int) bri->used)return(4);
+ if(src<0 || src>= (int) bri->used)return(5);
+ br_dst=&bri->rects[dst];
+ br_src=&bri->rects[src];
+ if( ddir == LDIR_RL && sdir == LDIR_LR){
+ if(br_dst->xur <= (br_src->xll + br_src->xur)/2.0){ status = 1; }
+ }
+ else if( ddir == LDIR_LR && sdir == LDIR_RL){
+ if((br_src->xll + br_src->xur)/2.0 <= br_dst->xll ){ status = 1; }
+ }
+ else if( ddir == LDIR_RL && sdir == LDIR_RL){
+ if(br_dst->xur <= (br_src->xll + br_src->xur)/2.0){ status = 1; }
+ }
+ else if( ddir == LDIR_LR && sdir == LDIR_LR){
+ if((br_src->xll + br_src->xur)/2.0 <= br_dst->xll ){ status = 1; }
+ }
+ return(status);
+}
+
+
+/**
+ \brief Try to deduce justification of a paragraph from the bounding rectangles for two successive lines.
+ \returns one of TR_PARA_ UJ (unknown justified), LJ, CJ, or RJ (left, center, or right justified).
+ \param bri pointer to the BR_INFO structure
+ \param dst index of the destination bounding rectangle.
+ \param src index of the source bounding rectangle.
+ \param slop allowed error in edge alignment.
+ \param type Preexisting justification for dst, if any. Justification of dst and src must match this or
+ TR_PARA_UJ is returned even if dst and src have some (other) alignment.
+*/
+enum tr_classes brinfo_pp_alignment(const BR_INFO *bri, int dst, int src, double slop, enum tr_classes type){
+ enum tr_classes newtype;
+ BRECT_SPECS *br_dst = & bri->rects[dst];
+ BRECT_SPECS *br_src = & bri->rects[src];
+ if((br_dst->yur >= br_src->yur) || (br_dst->yll >= br_src->yll)){ /* Y is positive DOWN */
+ /* lines in the wrong vertical order, no paragraph possible (Y is positive down) */
+ newtype = TR_PARA_UJ;
+ }
+ else if(fabs(br_dst->xll - br_src->xll) < slop){
+ /* LJ (might also be CJ but LJ takes precedence) */
+ newtype = TR_PARA_LJ;
+ }
+ else if(fabs(br_dst->xur - br_src->xur) < slop){
+ /* RJ */
+ newtype = TR_PARA_RJ;
+ }
+ else if(fabs( (br_dst->xur + br_dst->xll)/2.0 - (br_src->xur + br_src->xll)/2.0 ) < slop){
+ /* CJ */
+ newtype = TR_PARA_CJ;
+ }
+ else {
+ /* not aligned */
+ newtype = TR_PARA_UJ;
+ }
+ /* within a paragraph type can change from unknown to known, but not from one known type to another*/
+ if((type != TR_PARA_UJ) && (newtype != type)){
+ newtype = TR_PARA_UJ;
+ }
+/*
+printf("pp_align newtype:%d brects (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n",
+newtype,
+(br_dst->xll),
+(br_dst->yll),
+(br_dst->xur),
+(br_dst->yur),
+(br_src->xll),
+(br_src->yll),
+(br_src->xur),
+(br_src->yur));
+*/
+ return(newtype);
+}
+
+/**
+ \brief Release a BR_INFO structure. Release all associated memory.
+ use like: bri = brinfo_release(bri);
+ \param bri pointer to the BR_INFO structure.
+ \returns NULL.
+*/
+BR_INFO *brinfo_release(BR_INFO *bri){
+ if(bri){
+ free(bri->rects);
+ free(bri); /* release the overall brinfo structure */
+ }
+ return NULL;
+}
+
+
+
+/**
+ \brief Initialize an TR_INFO structure. Holds all data for text reassembly.
+ \returns a pointer to the TR_INFO structure created, or NULL on error.
+*/
+TR_INFO *trinfo_init(TR_INFO *tri){
+ if(tri)return(tri); /* tri is already set, double initialization is not allowed */
+ if(!(tri = (TR_INFO *)calloc(1,sizeof(TR_INFO))) ||
+ !(tri->fti = ftinfo_init()) ||
+ !(tri->tpi = tpinfo_init()) ||
+ !(tri->bri = brinfo_init()) ||
+ !(tri->cxi = cxinfo_init())
+ ){ tri = trinfo_release(tri); }
+ tri->out = NULL; /* This will allocate as needed, it might not ever be needed. */
+ tri->qe = 0.0;
+ tri->esc = 0.0;
+ tri->x = DBL_MAX;
+ tri->y = DBL_MAX;
+ tri->dirty = 0;
+ tri->use_kern = 1;
+ tri->load_flags = FT_LOAD_NO_SCALE;
+ tri->kern_mode = FT_KERNING_UNSCALED;
+ tri->outspace = 0;
+ tri->outused = 0;
+ tri->usebk = BKCLR_NONE;
+ memset(&(tri->bkcolor),0,sizeof(TRCOLORREF));
+ return(tri);
+}
+
+/**
+ \brief Release a TR_INFO structure completely.
+ Release all associated memory, including FontConfig.
+ See also trinfo_clear() and trinfo_release_except_FC().
+ use like: tri = trinfo_release(tri);
+ \param tri pointer to the TR_INFO structure.
+ \returns NULL.
+*/
+TR_INFO *trinfo_release(TR_INFO *tri){
+ if(tri){
+ if(tri->bri)tri->bri=brinfo_release(tri->bri);
+ if(tri->tpi)tri->tpi=tpinfo_release(tri->tpi);
+ if(tri->fti)tri->fti=ftinfo_release(tri->fti);
+ if(tri->cxi)tri->cxi=cxinfo_release(tri->cxi);
+ if(tri->out){ free(tri->out); tri->out=NULL; };
+ free(tri);
+ }
+ return(NULL);
+}
+
+/**
+ \brief Release a TR_INFO structure mostly.
+ Release all associated memory EXCEPT Fontconfig.
+ Fontconfig may still be needed elsewhere in a program and there is no way to figure that out here.
+ See also trinfo_clear() and trinfo_release().
+ use like: tri = trinfo_release_except_FC(tri);
+ \param tri pointer to the TR_INFO structure.
+ \returns NULL.
+*/
+TR_INFO *trinfo_release_except_FC(TR_INFO *tri){
+ if(tri){
+ if(tri->bri)tri->bri=brinfo_release(tri->bri);
+ if(tri->tpi)tri->tpi=tpinfo_release(tri->tpi);
+ if(tri->fti)tri->fti=ftinfo_clear(tri->fti);
+ if(tri->cxi)tri->cxi=cxinfo_release(tri->cxi);
+ if(tri->out){ free(tri->out); tri->out=NULL; };
+ free(tri);
+ }
+ return(NULL);
+}
+
+/**
+ \brief Clear a TR_INFO structure.
+ Releases text and rectangle information, but retains font information, both
+ Freetype information and Fontconfig information.
+ See also trinfo_release() and trinfo_release_except_FC().
+ Use like: tri = trinfo_clear(tri);
+ \param tri pointer to the TR_INFO structure.
+ \returns NULL.
+*/
+TR_INFO *trinfo_clear(TR_INFO *tri){
+ if(tri){
+
+ if(tri->bri)tri->bri=brinfo_release(tri->bri);
+ if(tri->tpi)tri->tpi=tpinfo_release(tri->tpi);
+ if(tri->cxi)tri->cxi=cxinfo_release(tri->cxi);
+ if(tri->out){
+ free(tri->out);
+ tri->out = NULL;
+ tri->outused = 0;
+ tri->outspace = 0;
+ };
+ /* Do NOT modify: qe, use_kern, usebk, load_flags, kern_mode, or bkcolor. Set the rest back to their defaults */
+ tri->esc = 0.0;
+ tri->x = DBL_MAX;
+ tri->y = DBL_MAX;
+ tri->dirty = 0;
+ if(!(tri->tpi = tpinfo_init()) || /* re-init the pieces just released */
+ !(tri->bri = brinfo_init()) ||
+ !(tri->cxi = cxinfo_init())
+ ){
+ tri = trinfo_release(tri); /* something horrible happened, clean out tri and return NULL */
+ }
+ }
+ return(tri);
+}
+
+
+/**
+ \brief Set the quantization error value for a TR_INFO structure.
+ If coordinates have passed through an integer form limits
+ in accuracy may have been imposed. For instance, if the X coordinate of a point in such a file
+ is 1000, and the conversion factor from those coordinates to points is .04, then eq is .04. This
+ just says that single coordinates are only good to within .04, and two coordinates may differ by as much
+ as .08, just due to quantization error. So if some calculation shows a difference of
+ .02 it may be interpreted as this sort of error and set to 0.0.
+ \returns 0 on success, !0 on error.
+ \param tri pointer to TR_INFO structure
+ \param qe quantization error.
+*/
+int trinfo_load_qe(TR_INFO *tri, double qe){
+ if(!tri)return(1);
+ if(qe<0.0)return(2);
+ tri->qe=qe;
+ return(0);
+}
+
+/**
+ \brief Set the background color and whether or not to use it.
+ When background color is turned on each line of text is underwritten with a rectangle
+ of the specified color. The rectangle is the merged bounding rectangle for that line.
+ \returns 0 on success but nothing changed, >0 on error, <0 on success and a value changed.
+ \param tri pointer to TR_INFO structure
+ \param usebk 0 for no background, anything else uses background color
+ \param bkcolor background color to use
+*/
+int trinfo_load_bk(TR_INFO *tri, int usebk, TRCOLORREF bkcolor){
+ int status=0;
+ if(!tri){ status = 1; }
+ else {
+ if((usebk < BKCLR_NONE) || (usebk > BKCLR_ALL)){ status = 2; }
+ else {
+ status = trinfo_check_bk(tri, usebk, bkcolor);
+ tri->usebk = usebk;
+ tri->bkcolor = bkcolor;
+ }
+ }
+ return(status);
+}
+
+/**
+ \brief Are the proposed new background and background color a change?
+ \returns 0 if they are the same, -1 if either is different
+ \param tri pointer to TR_INFO structure
+ \param usebk 0 for no background, anything else uses background color
+ \param bkcolor background color to use
+*/
+int trinfo_check_bk(TR_INFO *tri, int usebk, TRCOLORREF bkcolor){
+ int status = 0;
+ if( (tri->usebk != usebk) || memcmp(&tri->bkcolor,&bkcolor,sizeof(TRCOLORREF))){ status = -1; }
+ return(status);
+}
+
+/**
+ \brief Set Freetype parameters and kerning mode (if any) in a TRI_INFO structure.
+ \returns 0 on success, !0 on error.
+ \param tri pointer to a TR_INFO structure
+ \param use_kern 0 if kerning is to be employed, !0 otherwise.
+ \param load_flags Controls internal advance:
+ FT_LOAD_NO_SCALE, internal advance is in 1/64th of a point. (kerning values are still scaled)
+ FT_LOAD_TARGET_NORMAL internal advance is in 1/64th of a point. The scale
+ factor seems to be (Font Size in points)*(DPI)/(32.0 pnts)*(72 dpi).
+ \param kern_mode FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED. Set to match calling application.
+*/
+int trinfo_load_ft_opts(TR_INFO *tri, int use_kern, int load_flags, int kern_mode){
+ if(!tri)return(1);
+ tri->use_kern = use_kern;
+ tri->load_flags = load_flags;
+ tri->kern_mode = kern_mode;
+ return(0);
+}
+
+/**
+ \brief Append text to a TR_INFO struct's output buffer, expanding it if necessary.
+ \returns 0 on success, !0 on error.
+ \param tri pointer to a TR_INFO structure
+ \param src Pointer to a text string.
+*/
+int trinfo_append_out(TR_INFO *tri, const char *src){
+ size_t slen;
+ uint8_t *tmp;
+ if(!src)return(-1);
+ slen = strlen(src);
+ if(tri->outused + (int) slen + 1 >= tri->outspace){
+ tri->outspace += TEREMAX(ALLOCOUT_CHUNK,slen+1);
+ tmp = realloc(tri->out, tri->outspace * sizeof(uint8_t) );
+ if(tmp){ tri->out = tmp; }
+ else { return(-1); }
+ }
+ memcpy(tri->out + tri->outused, src, slen+1); /* copy the terminator */
+ tri->outused += slen; /* do not count the terminator in the length */
+ return(0);
+}
+
+
+/**
+ \brief Load a text object into a TR_INFO struct.
+ \returns 0 on success, !0 on error. -1 means that the escapement is different from the objects already loaded.
+ \param tri pointer to a TR_INFO structure
+ \param tsp pointer to a TCHUNK_SPECS structure (text object to load)
+ \param escapement angle in degrees of the text object.
+ \param flags special processing flags:
+ TR_EMFBOT calculate Y coordinates of ALIBOT object compatible with EMF files TA_BOTTOM alignment.
+*/
+int trinfo_load_textrec(TR_INFO *tri, const TCHUNK_SPECS *tsp, double escapement, int flags){
+
+ int status;
+ double x,y,xe;
+ double asc,dsc; /* these are the ascender/descender for the actual text */
+ int ymin,ymax;
+ double fasc,fdsc; /* these are the ascender/descender for the font as a whole (text independent) */
+ TP_INFO *tpi;
+ FT_INFO *fti;
+ BR_INFO *bri;
+ int current,idx,taln;
+ uint32_t prev;
+ uint32_t *text32,*tptr;
+ FNT_SPECS *fsp;
+ BRECT_SPECS bsp;
+
+ /* check incoming parameters */
+ if(!tri)return(1);
+ if(!tsp)return(2);
+ if(!tsp->string)return(3);
+ fti = tri->fti;
+ tpi = tri->tpi;
+ bri = tri->bri;
+ idx = tsp->fi_idx;
+ taln = tsp->taln;
+ if(!fti->used)return(4);
+ if(idx <0 || idx >= (int) fti->used)return(5);
+ fsp = &(fti->fonts[idx]);
+
+ if(!tri->dirty){
+ tri->x = tsp->x;
+ tri->y = tsp->y;
+ tri->esc = escapement;
+ tri->dirty = 1;
+ }
+ else {
+ if(tri->esc != escapement)return(-1);
+ }
+
+
+ tpinfo_insert(tpi,tsp);
+ current=tpi->used-1;
+ ymin = 64000;
+ ymax = -64000;
+
+ /* The geometry model has origin Y at the top of screen, positive Y is down, maximum positive
+ Y is at the bottom of the screen. That makes "top" (by positive Y) actually the bottom
+ (as viewed on the screen.) */
+
+ escapement *= 2.0 * M_PI / 360.0; /* degrees to radians */
+ x = tpi->chunks[current].x - tri->x; /* convert to internal orientation */
+ y = tpi->chunks[current].y - tri->y;
+ tpi->chunks[current].x = x * cos(escapement) - y * sin(escapement); /* coordinate transformation */
+ tpi->chunks[current].y = x * sin(escapement) + y * cos(escapement);
+
+/* Careful! face bbox does NOT scale with FT_Set_Char_Size
+printf("Face idx:%d bbox: xMax/Min:%ld,%ld yMax/Min:%ld,%ld UpEM:%d asc/des:%d,%d height:%d size:%f\n",
+ idx,
+ fsp->face->bbox.xMax,fsp->face->bbox.xMin,
+ fsp->face->bbox.yMax,fsp->face->bbox.yMin,
+ fsp->face->units_per_EM,fsp->face->ascender,fsp->face->descender,fsp->face->height,fsp->fsize);
+*/
+
+ text32 = U_Utf8ToUtf32le((char *) tsp->string,0,NULL);
+ if(!text32){ // LATIN1 encoded >128 are generally not valid UTF, so the first will fail
+ text32 = U_Latin1ToUtf32le((char *) tsp->string,0,NULL);
+ if(!text32)return(5);
+ }
+ /* baseline advance is independent of character orientation */
+ for(xe=0.0, prev=0, tptr=text32; *tptr; tptr++){
+ status = TR_getadvance(fti, fsp, *tptr, (tri->use_kern ? prev: 0), tri->load_flags, tri->kern_mode, &ymin, &ymax);
+ if(status>=0){
+ xe += ((double) status)/64.0;
+ }
+ else { return(6); }
+ prev=*tptr;
+ }
+
+ /* Some glyphs in fonts have no vertical extent, for instance, Hebrew glyphs in Century Schoolbook L.
+ Use the 3/4 of the font size as a (very bad) approximation for the actual values. */
+ if(ymin==0 && ymax==0){
+ ymax = 0.75 * fsp->fsize * 64.0;
+ }
+
+ asc = ((double) (ymax))/64.0;
+ dsc = ((double) (ymin))/64.0; /* This is negative */
+/* This did not work very well because the ascender/descender went well beyond the actual characters, causing
+ overlaps on lines that did not actually overlap (vertically).
+ asc = ((double) (fsp->face->ascender) )/64.0;
+ dsc = ((double) (fsp->face->descender))/64.0;
+*/
+
+ free(text32);
+
+ /* find the font ascender descender (general one, not specific for current text) */
+ fasc = ((double) (fsp->face->ascender) )/64.0;
+ fdsc = ((double) (fsp->face->descender))/64.0;
+
+ /* originally the denominator was just 32.0, but it broke when units_per_EM wasn't 2048 */
+ double fixscale = tsp->fs/(((double) fsp->face->units_per_EM)/64.0);
+ if(tri->load_flags & FT_LOAD_NO_SCALE) xe *= fixscale;
+
+ /* now place the rectangle using ALN information */
+ if( taln & ALIHORI & ALILEFT ){
+ bsp.xll = tpi->chunks[current].x;
+ bsp.xur = tpi->chunks[current].x + xe;
+ }
+ else if( taln & ALIHORI & ALICENTER){
+ bsp.xll = tpi->chunks[current].x - xe/2.0;
+ bsp.xur = tpi->chunks[current].x + xe/2.0;
+ }
+ else{ /* taln & ALIHORI & ALIRIGHT */
+ bsp.xll = tpi->chunks[current].x - xe;
+ bsp.xur = tpi->chunks[current].x;
+ }
+ tpi->chunks[current].ldir = tsp->ldir;
+
+ if(tri->load_flags & FT_LOAD_NO_SCALE){
+ asc *= fixscale;
+ dsc *= fixscale;
+ fasc *= fixscale;
+ fdsc *= fixscale;
+ }
+
+
+ /* From this point forward y is on the baseline, so need to correct it in chunks. The asc/dsc are the general
+ ones for the font, else the text content will muck around with the baseline in BAD ways. */
+ if( taln & ALIVERT & ALITOP ){ tpi->chunks[current].y += fasc; }
+ else if( taln & ALIVERT & ALIBASE){ } /* no correction required */
+ else{ /* taln & ALIVERT & ALIBOT */
+ if(flags & TR_EMFBOT){ tpi->chunks[current].y -= 0.35 * tsp->fs; } /* compatible with EMF implementations */
+ else { tpi->chunks[current].y += fdsc; }
+ }
+ tpi->chunks[current].boff = -dsc;
+
+ /* since y is always on the baseline, the lower left and upper right are easy. These use asc/dsc for the particular text,
+ so that the bounding box will fit it tightly. */
+ bsp.yll = tpi->chunks[current].y - dsc;
+ bsp.yur = tpi->chunks[current].y - asc;
+ brinfo_insert(bri,&bsp);
+ tpi->chunks[current].rt_tidx = bri->used - 1; /* index of rectangle that contains it */
+
+ return(0);
+}
+
+/**
+ \brief Fontweight conversion. Fontconfig units to SVG units.
+ Anything not recognized becomes "normal" == 400.
+ There is no interpolation because a value that mapped to 775, for instance, most
+ likely would not display properly because it is intermediate between 700 and 800, and
+ only those need be supported in SVG viewers.
+ \returns SVG font weight
+ \param weight Fontconfig font weight.
+*/
+int TR_weight_FC_to_SVG(int weight){
+ int ret=400;
+ if( weight == 0){ ret = 100; }
+ else if(weight == 40){ ret = 200; }
+ else if(weight == 50){ ret = 300; }
+ else if(weight == 80){ ret = 400; }
+ else if(weight == 100){ ret = 500; }
+ else if(weight == 180){ ret = 600; }
+ else if(weight == 200){ ret = 700; }
+ else if(weight == 205){ ret = 800; }
+ else if(weight == 210){ ret = 900; }
+ else { ret = 400; }
+ return(ret);
+}
+
+/**
+ \brief Set the padding that will be added to bounding rectangles before checking for overlaps in brinfo_overlap().
+ \returns void
+ \param rt_pad pointer to an RT_PAD structure.
+ \param up padding for the top of a bounding rectangle.
+ \param down padding for the bottom of a bounding rectangle.
+ \param left padding for the left of a bounding rectangle.
+ \param right padding for the right of a bounding rectangle.
+*/
+void TR_rt_pad_set(RT_PAD *rt_pad, double up, double down, double left, double right){
+ rt_pad->up = up;
+ rt_pad->down = down;
+ rt_pad->left = left;
+ rt_pad->right = right;
+}
+
+/**
+ \brief Convert from analyzed complexes to SVG format.
+ \returns void
+ \param tri pointer to a TR_INFO struct which will be analyzed. Result is stored in its "out" buffer.
+*/
+void TR_layout_2_svg(TR_INFO *tri){
+ double x = tri->x;
+ double y = tri->y;
+ double dx,dy;
+ double esc;
+ double recenter; /* horizontal offset to set things up correctly for CJ and RJ text, is 0 for LJ*/
+ double lineheight=1.25;
+ int cutat;
+ FT_INFO *fti=tri->fti; /* Font info storage */
+ TP_INFO *tpi=tri->tpi; /* Text Info/Position Info storage */
+ BR_INFO *bri=tri->bri; /* bounding Rectangle Info storage */
+ CX_INFO *cxi=tri->cxi; /* Complexes deduced for this text */
+ TCHUNK_SPECS *tsp; /* current text object */
+ CX_SPECS *csp;
+ CX_SPECS *cline_sp;
+ unsigned int i,j,k,jdx,kdx;
+ int ldir;
+ char obuf[1024]; /* big enough for style and so forth */
+ char cbuf[16]; /* big enough for one hex color */
+
+ char stransform[128];
+ double newx,newy,tmpx;
+ uint32_t utmp;
+
+ /* copy the current numeric locale, make a copy because setlocale may stomp on
+ the memory it points to. Then change it because SVG needs decimal points,
+ not commas, in floats. Restore on exit from this routine.
+ */
+ char *prev_locale = setlocale(LC_NUMERIC,NULL);
+ char *hold_locale = malloc(sizeof(char) * (strlen(prev_locale) + 1));
+ strcpy(hold_locale,prev_locale);
+ (void) setlocale(LC_NUMERIC,"POSIX");
+
+/*
+#define DBG_TR_PARA 0
+#define DBG_TR_INPUT 1
+*/
+ /* The debug section below is difficult to see if usebk is anything other than BKCLR_NONE */
+#if DBG_TR_PARA || DBG_TR_INPUT /* enable debugging code, writes extra information into SVG */
+ /* put rectangles down for each text string - debugging!!! This will not work properly for any Narrow fonts */
+ esc = tri->esc;
+ esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */
+ sprintf(stransform,"transform=\"matrix(%f,%f,%f,%f,%f,%f)\"\n",cos(esc),-sin(esc),sin(esc),cos(esc), 1.25*x,1.25*y);
+ for(i=cxi->phase1; i<cxi->used;i++){ /* over all complex members from phase2 == TR_PARA_* complexes */
+ csp = &(cxi->cx[i]);
+ for(j=0; j<csp->kids.used; j++){ /* over all members of these complexes, which are phase1 complexes */
+ jdx = csp->kids.members[j]; /* index of phase1 complex (all are TR_TEXT or TR_LINE) */
+ for(k=0; k<cxi->cx[jdx].kids.used; k++){ /* over all members of the phase1 complex */
+ kdx = cxi->cx[jdx].kids.members[k]; /* index for text objects in tpi */
+ tsp = &tpi->chunks[kdx];
+ ldir = tsp->ldir;
+ if(!j && !k){
+#if DBG_TR_PARA
+ TRPRINT(tri, "<rect\n");
+ TRPRINT(tri, "style=\"color:#0000FF;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:none;stroke:#000000;stroke-width:0.8;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n");
+ sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[csp->rt_cidx].xur - bri->rects[csp->rt_cidx].xll));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[csp->rt_cidx].yll - bri->rects[csp->rt_cidx].yur));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"x=\"%f\" y=\"%f\"\n",1.25*(bri->rects[csp->rt_cidx].xll),1.25*(bri->rects[csp->rt_cidx].yur));
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, stransform);
+ TRPRINT(tri, "/>\n");
+#endif /* DBG_TR_PARA */
+ }
+#if DBG_TR_INPUT /* debugging code, this section writes the original text objects */
+ newx = 1.25*(ldir == LDIR_RL ? bri->rects[tsp->rt_tidx].xur : bri->rects[tsp->rt_tidx].xll);
+ newy = 1.25*(bri->rects[tsp->rt_tidx].yur);
+ TRPRINT(tri, "<rect\n");
+ TRPRINT(tri, "style=\"color:#000000;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:none;stroke:#00FF00;stroke-width:0.3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n");
+ sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[tsp->rt_tidx].xur - bri->rects[tsp->rt_tidx].xll));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[tsp->rt_tidx].yll - bri->rects[tsp->rt_tidx].yur));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"x=\"%f\" y=\"%f\"\n",1.25*(bri->rects[tsp->rt_tidx].xll),newy);
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, stransform);
+ TRPRINT(tri, "/>\n");
+
+ newy = 1.25*(bri->rects[tsp->rt_tidx].yll - tsp->boff);
+ sprintf(obuf,"<text x=\"%f\" y=\"%f\"\n",newx, newy );
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"xml:space=\"preserve\"\n");
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, stransform);
+ TRPRINT(tri, "style=\"fill:#FF0000;");
+ sprintf(obuf,"font-size:%fpx;",tsp->fs*1.25); /*IMPORTANT, if the FS is given in pt it looks like crap in browsers. As if px != 1.25 pt, maybe 96 dpi not 90?*/
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"font-style:%s;",(tsp->italics ? "italic" : "normal"));
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, "font-variant:normal;");
+ sprintf(obuf,"font-weight:%d;",TR_weight_FC_to_SVG(tsp->weight));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"font-stretch:%s;",(tsp->condensed==100 ? "Normal" : "Condensed"));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"text-anchor:%s;",(tsp->ldir == LDIR_RL ? "end" : "start"));
+ TRPRINT(tri, obuf);
+ cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fontspec,":");
+ sprintf(obuf,"font-family:%.*s;",cutat,fti->fonts[tsp->fi_idx].fontspec);
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"\n\">%s</text>\n",&tsp->string[tsp->spaces]);
+ TRPRINT(tri, obuf);
+#endif /* DBG_TR_INPUT debugging code, original text objects */
+ }
+ }
+ }
+#endif /* DBG_TR_PARA and/or DBG_TR_INPUT */
+
+
+ if(tri->usebk){
+ esc = tri->esc;
+ esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */
+ sprintf(stransform,"transform=\"matrix(%f,%f,%f,%f,%f,%f)\"\n",cos(esc),-sin(esc),sin(esc),cos(esc), 1.25*x,1.25*y);
+
+ for(i=cxi->phase1; i<cxi->used;i++){ /* over all complex members from phase2 == TR_PARA_* complexes */
+ TRPRINT(tri, "<g>\n"); /* group backgrounds for each <text> object in the SVG */
+ csp = &(cxi->cx[i]);
+ for(j=0; j<csp->kids.used; j++){ /* over all members of these complexes, which are phase1 complexes */
+ jdx = csp->kids.members[j]; /* index of phase1 complex (all are TR_TEXT or TR_LINE) */
+ cline_sp = &(cxi->cx[jdx]);
+ if(tri->usebk == BKCLR_LINE){
+ TRPRINT(tri, "<rect\n");
+ sprintf(obuf,"style=\"color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#%2.2X%2.2X%2.2X;;stroke:none;;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n",tri->bkcolor.Red,tri->bkcolor.Green,tri->bkcolor.Blue);
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[cline_sp->rt_cidx].xur - bri->rects[cline_sp->rt_cidx].xll));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[cline_sp->rt_cidx].yll - bri->rects[cline_sp->rt_cidx].yur));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"x=\"%f\" y=\"%f\"\n",1.25*(bri->rects[cline_sp->rt_cidx].xll),1.25*(bri->rects[cline_sp->rt_cidx].yur));
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, stransform);
+ TRPRINT(tri, "/>\n");
+ }
+
+ for(k=0; k<cxi->cx[jdx].kids.used; k++){ /* over all members of the phase1 complex */
+ kdx = cxi->cx[jdx].kids.members[k]; /* index for text objects in tpi */
+ tsp = &tpi->chunks[kdx];
+ ldir = tsp->ldir;
+ if(!j && !k){
+ if(tri->usebk == BKCLR_ALL){
+ TRPRINT(tri, "<rect\n");
+ sprintf(obuf,"style=\"color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#%2.2X%2.2X%2.2X;;stroke:none;;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n",tri->bkcolor.Red,tri->bkcolor.Green,tri->bkcolor.Blue);
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[csp->rt_cidx].xur - bri->rects[csp->rt_cidx].xll));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[csp->rt_cidx].yll - bri->rects[csp->rt_cidx].yur));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"x=\"%f\" y=\"%f\"\n",1.25*(bri->rects[csp->rt_cidx].xll),1.25*(bri->rects[csp->rt_cidx].yur));
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, stransform);
+ TRPRINT(tri, "/>\n");
+ }
+ }
+ if(tri->usebk == BKCLR_FRAG){
+ newx = 1.25*(ldir == LDIR_RL ? bri->rects[tsp->rt_tidx].xur : bri->rects[tsp->rt_tidx].xll);
+ newy = 1.25*(bri->rects[tsp->rt_tidx].yur);
+ TRPRINT(tri, "<rect\n");
+ sprintf(obuf,"style=\"color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#%2.2X%2.2X%2.2X;;stroke:none;;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n",tri->bkcolor.Red,tri->bkcolor.Green,tri->bkcolor.Blue);
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[tsp->rt_tidx].xur - bri->rects[tsp->rt_tidx].xll));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[tsp->rt_tidx].yll - bri->rects[tsp->rt_tidx].yur));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"x=\"%f\" y=\"%f\"\n",newx,newy);
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, stransform);
+ TRPRINT(tri, "/>\n");
+ }
+ }
+ }
+ TRPRINT(tri, "</g>\n"); /* end of grouping for backgrounds for each <text> object in the SVG */
+ }
+ }
+
+
+ /* over all complex members from phase2. Paragraphs == TR_PARA_* */
+ for(i=cxi->phase1; i<cxi->used;i++){
+ csp = &(cxi->cx[i]);
+ esc = tri->esc;
+ esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */
+
+ /* over all members of the present Paragraph. Each of these is a line and a phase 1 complex.
+ It may be either TR_TEXT or TR_LINE */
+ for(j=0; j<csp->kids.used; j++){
+ if(j){
+ sprintf(obuf,"</tspan>");
+ TRPRINT(tri, obuf);
+ }
+ jdx = csp->kids.members[j]; /* index of phase1 complex (all are TR_TEXT or TR_LINE) */
+ recenter = 0; /* mostly to quiet a compiler warning, should always be set below */
+
+
+ /* over all members of the present Line. These are the original text objects which were reassembled.
+ There will be one for TR_TEXT, more than one for TR_LINE */
+ for(k=0; k<cxi->cx[jdx].kids.used; k++){
+ kdx = cxi->cx[jdx].kids.members[k]; /* index for text objects in tpi, for this k */
+ tsp = &tpi->chunks[kdx]; /* text chunk for this k */
+ ldir = tsp->ldir; /* language direction for this k */
+ if(!k){ /* first iteration */
+ switch(csp->type){ /* set up the alignment, if there is one */
+ case TR_TEXT:
+ case TR_LINE:
+ /* these should never occur, this section quiets a compiler warning */
+ break;
+ case TR_PARA_UJ:
+ case TR_PARA_LJ:
+ if(ldir == LDIR_RL){ recenter = -(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll); }
+ else { recenter = 0.0; }
+ break;
+ case TR_PARA_CJ:
+ if(ldir == LDIR_RL){ recenter = -(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll)/2.0; }
+ else { recenter = +(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll)/2.0; }
+ break;
+ case TR_PARA_RJ:
+ if(ldir == LDIR_RL){ recenter = 0.0; }
+ else { recenter = +(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll); }
+ break;
+ }
+ if(!j){
+ TRPRINT(tri, "<text\n");
+ TRPRINT(tri, "xml:space=\"preserve\"\n");
+ TRPRINT(tri, "style=\"");
+ sprintf(obuf,"font-size:%fpx;",tsp->fs*1.25); /*IMPORTANT, if the FS is given in pt it looks like crap in browsers. As if px != 1.25 pt, maybe 96 dpi not 90?*/
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"font-style:%s;",(tsp->italics ? "italic" : "normal"));
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, "font-variant:normal;");
+ sprintf(obuf,"font-weight:%d;",TR_weight_FC_to_SVG(tsp->weight));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"font-stretch:%s;",(tsp->condensed==100 ? "Normal" : "Condensed"));
+ TRPRINT(tri, obuf);
+ if(tsp->vadvance){ lineheight = tsp->vadvance *100.0; }
+ else { lineheight = 125.0; }
+ sprintf(obuf,"line-height:%f%%;",lineheight);
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, "letter-spacing:0px;");
+ TRPRINT(tri, "word-spacing:0px;");
+ TRPRINT(tri, "fill:#000000;");
+ TRPRINT(tri, "fill-opacity:1;");
+ TRPRINT(tri, "stroke:none;");
+ cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fontspec,":");
+ sprintf(obuf,"font-family:%.*s;",cutat,fti->fonts[tsp->fi_idx].fontspec);
+ TRPRINT(tri, obuf);
+ switch(csp->type){ /* set up the alignment, if there is one */
+ case TR_TEXT:
+ case TR_LINE:
+ /* these should never occur, this section quiets a compiler warning */
+ break;
+ case TR_PARA_UJ:
+ case TR_PARA_LJ:
+ sprintf(obuf,"text-align:start;text-anchor:start;");
+ break;
+ case TR_PARA_CJ:
+ sprintf(obuf,"text-align:center;text-anchor:middle;");
+ break;
+ case TR_PARA_RJ:
+ sprintf(obuf,"text-align:end;text-anchor:end;");
+ break;
+ }
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, "\"\n"); /* End of style specification */
+ sprintf(obuf,"transform=\"matrix(%f,%f,%f,%f,%f,%f)\"\n",cos(esc),-sin(esc),sin(esc),cos(esc),1.25*x,1.25*y);
+ TRPRINT(tri, obuf);
+ tmpx = 1.25*((ldir == LDIR_RL ? bri->rects[kdx].xur : bri->rects[kdx].xll) + recenter);
+ sprintf(obuf,"x=\"%f\" y=\"%f\"\n>",tmpx,1.25*(bri->rects[kdx].yll - tsp->boff));
+ TRPRINT(tri, obuf);
+ }
+ tmpx = 1.25*((ldir == LDIR_RL ? bri->rects[kdx].xur : bri->rects[kdx].xll) + recenter);
+ sprintf(obuf,"<tspan sodipodi:role=\"line\"\nx=\"%f\" y=\"%f\"\n>",tmpx,1.25*(bri->rects[kdx].yll - tsp->boff));
+ TRPRINT(tri, obuf);
+ }
+ TRPRINT(tri, "<tspan\n");
+
+ /* Scale kerning and make any other necessary adjustments
+
+ */
+ dx = 1.25 * tsp->xkern;
+ dy = 1.25 * tsp->ykern;
+
+ sprintf(obuf,"dx=\"%f\" dy=\"%f\" ",dx, dy);
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"style=\"fill:#%2.2X%2.2X%2.2X;",tsp->color.Red,tsp->color.Green,tsp->color.Blue);
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"font-size:%fpx;",tsp->fs*1.25); /*IMPORTANT, if the FS is given in pt it looks like crap in browsers. As if px != 1.25 pt, maybe 96 dpi not 90?*/
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"font-style:%s;",(tsp->italics ? "italic" : "normal"));
+ TRPRINT(tri, obuf);
+ if(tsp->decoration & TXTDECOR_TMASK){
+ sprintf(obuf,"text-decoration:");
+ /* multiple text decoration styles may be set */
+ utmp = tsp->decoration & TXTDECOR_TMASK;
+ if(utmp & TXTDECOR_UNDER ){ strcat(obuf," underline"); }
+ if(utmp & TXTDECOR_OVER ){ strcat(obuf," overline"); }
+ if(utmp & TXTDECOR_BLINK ){ strcat(obuf," blink"); }
+ if(utmp & TXTDECOR_STRIKE){ strcat(obuf," line-through");}
+ if(*obuf){
+ /* only a single text decoration line type may be set */
+ switch(tsp->decoration & TXTDECOR_LMASK){
+ case TXTDECOR_SOLID: break; // "solid" is the CSS 3 default, omitting it remains CSS 2 compatible
+ case TXTDECOR_DOUBLE: strcat(obuf," double"); break; // these are all CSS3
+ case TXTDECOR_DOTTED: strcat(obuf," dotted"); break;
+ case TXTDECOR_DASHED: strcat(obuf," dashed"); break;
+ case TXTDECOR_WAVY: strcat(obuf," wavy" ); break;
+ default: break;
+ }
+ if((tsp->decoration & TXTDECOR_CLRSET) && memcmp(&(tsp->decColor),&(tsp->color),sizeof(TRCOLORREF))){
+ /* CSS 3, CSS 2 implementations may choke on it. If the specified color matches text color omit, for better CSS 2 compatitiblity. */
+ sprintf(cbuf," #%2.2X%2.2X%2.2X",tsp->decColor.Red,tsp->decColor.Green,tsp->decColor.Blue);
+ strcat(obuf,cbuf);
+ }
+ }
+ strcat(obuf,";");
+ TRPRINT(tri,obuf);
+ }
+ TRPRINT(tri, "font-variant:normal;");
+ sprintf(obuf,"font-weight:%d;",TR_weight_FC_to_SVG(tsp->weight));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"font-stretch:%s;",(tsp->condensed==100 ? "Normal" : "Condensed"));
+ TRPRINT(tri, obuf);
+ cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fontspec,":");
+ sprintf(obuf,"font-family:%.*s;\"",cutat,fti->fonts[tsp->fi_idx].fontspec);
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, "\n>");
+ TRPRINT(tri, (char *) tsp->string);
+ TRPRINT(tri, "</tspan>");
+ } /* end of k loop */
+ } /* end of j loop */
+ TRPRINT(tri,"</tspan></text>\n");
+ } /* end of i loop */
+
+ /* restore locale and free memory. */
+ (void) setlocale(LC_NUMERIC,hold_locale);
+ free(hold_locale);
+}
+
+/**
+ \brief Attempt to figure out the original organization, in lines and paragraphs, of the text objects.
+ The method is:
+ 1. Generate complexes from the text objects (strings) by overlaps (optionally allowing up to two spaces to be
+ added) to produce larger rectangles. Complexes that are more or less sequential and have 2 or more text objects
+ are TR_LINEs, therwise they are TR_TEXT.
+ 2. Group sequential complexes (TR_LINE or TR_TEXT) into TR_PARA_UJ (paragraphs,by smooth progression in vertical
+ position down page).
+ 3. Analyze the paragraphs to classify them as Left/Center/Right justified (possibly with indentation.) If
+ they do not fall into any of these categories break that one back down into TR_LINE/TR_TEXT.
+ 4. Return the number of complex text objects.
+ \returns Number of complexes. (>=1, <= number of text objects.) <0 is an error.
+ \param tri pointer to the TR_INFO structure holding the data, which will also hold the results.
+*/
+int TR_layout_analyze(TR_INFO *tri){
+ unsigned int i,j,k;
+ int ok;
+ int cxidx;
+ int src_rt;
+ int dst_rt;
+ TP_INFO *tpi;
+ BR_INFO *bri;
+ CX_INFO *cxi;
+ FT_INFO *fti;
+ BRECT_SPECS bsp;
+ RT_PAD rt_pad_i;
+ RT_PAD rt_pad_j;
+ double ratio;
+ double qsp,dx,dy;
+ double spcadv;
+ enum tr_classes type;
+ TCHUNK_SPECS *tspi;
+ TCHUNK_SPECS *tspj;
+ TCHUNK_SPECS *tspRevEnd=NULL;
+ TCHUNK_SPECS *tspRevStart=NULL;
+ CX_SPECS *csp;
+ CHILD_SPECS *kidp; /* used with preceding complex (see below) */
+ CHILD_SPECS *kidc; /* used with current complex (see below) */
+ int lastldir,ldir,rev;
+
+ if(!tri)return(-1);
+ if(!tri->cxi)return(-2);
+ if(!tri->tpi)return(-3);
+ if(!tri->bri)return(-4);
+ if(!tri->fti)return(-5);
+ tpi=tri->tpi;
+ cxi=tri->cxi;
+ bri=tri->bri;
+ fti=tri->fti;
+ cxi->lines = 0;
+ cxi->paras = 0;
+ cxi->phase1 = 0;
+
+/* When debugging
+ ftinfo_dump(fti);
+*/
+ /* Phase 1. Working sequentially, insert text. Initially as TR_TEXT and then try to extend to TR_LINE by checking
+ overlaps. When done the complexes will contain a mix of TR_LINE and TR_TEXT. */
+
+ for(i=0; i<tpi->used; i++){
+ tspi = &(tpi->chunks[i]);
+ memcpy(&bsp,&(bri->rects[tspi->rt_tidx]),sizeof(BRECT_SPECS)); /* Must make a copy as next call may reallocate rects! */
+ (void) brinfo_insert(bri,&bsp);
+ dst_rt = bri->used-1;
+ (void) cxinfo_insert(cxi, i, dst_rt, TR_TEXT);
+ cxidx = cxi->used-1;
+
+ spcadv = fti->fonts[tspi->fi_idx].spcadv * tspi->fs/32.0; /* spcadv was always FT_LOAD_NO_SCALE */
+ /* for the leading text: pad with no leading and two trailing spaces, leading and trailing depend on direction */
+ if(tspi->ldir == LDIR_RL){ TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, tri->qe + 2.0 * spcadv, 0.0); }
+ else { TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, 0.0, tri->qe + 2.0 * spcadv); }
+
+ for(j=i+1; j<tpi->used; j++){
+ tspj = &(tpi->chunks[j]);
+ /* Reject font size changes of greater than 50%, these are almost certainly not continuous text. These happen
+ in math formulas, for instance, where a sum or integral is much larger than the other symbols. */
+ ratio = (double)(tspj->fs)/(double)(tspi->fs);
+ if(ratio >2.0 || ratio <0.5)break;
+
+ spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0; /* spcadv was always FT_LOAD_NO_SCALE */
+ /* for the trailing text: pad with one leading and trailing spaces (so it should work L->R and R->L) */
+ TR_rt_pad_set(&rt_pad_j,tri->qe, tri->qe, spcadv, spcadv);
+ src_rt = tspj->rt_tidx;
+
+ /* Reject direction changes like [1 <- Hebrew][2 -> English], that is where the direction changes AND the
+ next logical piece of text is "upstream" positionally of its logical predecessor. The meaning of such
+ a construct is at best ambiguous. The test is only applied with respect to the first text chunk. This sort
+ of construct may appear when a valid initial construct like [1->English][2<-Hebrew][3->English] is edited
+ and the leading chunk of text removed.
+
+ Also reject reversed order text as in (English) <A><B><C> (draw order) arranged as <C><B><A>. This happens
+ if the language direction field is incorrect, perhaps due to a corrupt or malformed input file.
+ */
+ if(brinfo_upstream(bri,
+ dst_rt, /* index into bri for dst */
+ src_rt, /* index into bri for src */
+ tspi->ldir,tspj->ldir))break;
+
+ if(!brinfo_overlap(bri,
+ dst_rt, /* index into bri for dst */
+ src_rt, /* index into bri for src */
+ &rt_pad_i,&rt_pad_j)){
+ (void) cxinfo_append(cxi,j,TR_LINE);
+ (void) brinfo_merge(bri,dst_rt,src_rt);
+ /* for the leading text: pad with two leading and trailing spaces (so it should work L->R and R->L */
+ spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0; /* spcadv was always FT_LOAD_NO_SCALE */
+ TR_rt_pad_set(&rt_pad_i, tri->qe, tri->qe,
+ tri->qe + 2.0 * spcadv, tri->qe + 2.0 * spcadv);
+ }
+ else { /* either alignment ge*/
+ break;
+ }
+ }
+
+ /* Bidirectional text will cause complexes to not assemble in one pass.
+ This happens whenever a change of direction occurs with 2 or more sequential elements in
+ the opposite direction,
+
+ Let + = LR and - = RL.
+
+ Reading left to right, this happens with +-- or -++.
+ For instance, the sequence ++-+ ---+ would break into the two complexes shown.
+ Not until the last element in the second complex is added will the bounding rectangles for the complexes overlap.
+
+ Check for this effect now if there is a preceding complex and the first element of the current complex is
+ reversed from the last in the preceding. */
+ if(cxidx >= 1){
+ kidp = &(cxi->cx[cxidx-1].kids);
+ kidc = &(cxi->cx[cxidx ].kids);
+ tspi = &(tpi->chunks[ kidp->members[kidp->used - 1] ]); /* here, the last text element in preceding complex */
+ tspj = &(tpi->chunks[ kidc->members[0 ] ]); /* here, tge first text element in current complex */
+ if(tspi->ldir != tspj->ldir){
+ spcadv = fti->fonts[tspi->fi_idx].spcadv * tspi->fs/32.0;
+ if(tspi->ldir == LDIR_RL){ TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, tri->qe + 2.0 * spcadv, 0.0); }
+ else { TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, 0.0, tri->qe + 2.0 * spcadv); }
+ spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0;
+ TR_rt_pad_set(&rt_pad_j,tri->qe, tri->qe, spcadv, spcadv);
+ if(!brinfo_overlap(bri,
+ cxi->cx[cxidx-1].rt_cidx, /* index into rt for dst cx */
+ cxi->cx[cxidx].rt_cidx, /* index into rt for src cx */
+ &rt_pad_i,&rt_pad_j)){
+ /* Merge the current complex into the preceding one*/
+ (void) cxinfo_merge(cxi, cxidx-1, cxidx, TR_LINE);
+ (void) brinfo_merge(bri,cxi->cx[cxidx-1].rt_cidx,cxi->cx[cxidx].rt_cidx); /* merge the bounding boxes*/
+ (void) cxinfo_trim(cxi);
+ cxi->lines--; /* else the normal line count value is one too high */
+ /* remove the current complex */
+ }
+ }
+ }
+
+ if(cxi->cx[cxidx].type == TR_LINE)cxi->lines++;
+ i=j-1; /* start up after the last merged entry (there may not be any) */
+ }
+ cxi->phase1 = cxi->used; /* total complexes defined in this phase, all TR_LINE or TR_TEXT */
+
+ /* phase 1.5, calculate kerning. This is as good a place to do it as any. At this point all kern values
+ are zero. Each of these pieces is strictly unidirectional, but each piece can have a different direction.
+ The direction of the line is set by the first text element. The ends of runs of elements which are
+ reversed with respect to the line direction are special, everything else is simple:
+ Let: + == L->R, - == R->L, $ == end of text, the rules for kerning on B are:
+ A B others xkern
+ [+|$] + + [+|$] Bll - Aur
+ [-|$] - - [-|$] All - Bur (chs)
+ + - + [-|$] Bll - Aur (chs)
+ - + - [+|$] All - Bur
+ + - -...[-=C] [+|$] All - Cur (chs)
+ - + +...[+=C] [-|$] Cll - Aur
+
+ chs = change sign, because dx is an absolute direction, and direction of text on RTL is in -x.
+
+ Kerning calculations currently seems unstable for R->L if the kerning extends to the end of the line. If
+ the first and last characters are back in sync there are no issues. When things go south R->L left justified
+ text is not justified when read in.
+ */
+
+ for(i=0; i < cxi->phase1; i++){ /* over all lines */
+ csp = &(cxi->cx[i]);
+ if(csp->kids.used < 2)continue; /* no kerning possible */
+ tspi = &tpi->chunks[csp->kids.members[0]]; /* used here as last tsp. no kerning is applied to the first element */
+ lastldir = ldir = tspi->ldir;
+ rev = 0; /* the first ldir defines forward and reverse */
+ for(j=1; j<csp->kids.used; j++){
+ tspj = &tpi->chunks[csp->kids.members[j]];
+ ldir = tspj->ldir;
+ if(ldir != lastldir){ /* direction change */
+ rev = !rev; /* reverse direction tracker */
+ if(!rev){ /* back in original orientation */
+ if(ldir == LDIR_RL){ tspj->xkern = bri->rects[tspj->rt_tidx].xur - bri->rects[tspRevStart->rt_tidx].xll; }
+ else { tspj->xkern = bri->rects[tspj->rt_tidx].xll - bri->rects[tspRevStart->rt_tidx].xur; }
+ tspj->ykern = (bri->rects[tspj->rt_tidx].yll - tspj->boff) -
+ (bri->rects[tspRevStart->rt_tidx].yll - tspRevStart->boff);
+ }
+ else { /* now in reversed orientation */
+ tspRevStart = tspj; /* Save the beginning of this run (length >=1 ) */
+ /* scan forward for the last text object in this orientation, include the first */
+ for(k=j; k <csp->kids.used; k++){
+ if(tpi->chunks[csp->kids.members[k]].ldir == ldir){ tspRevEnd = &tpi->chunks[csp->kids.members[k]]; }
+ else { break; }
+ }
+ if(lastldir == LDIR_RL){ tspj->xkern = bri->rects[tspRevEnd->rt_tidx].xur - bri->rects[tspi->rt_tidx].xll; }
+ else { tspj->xkern = bri->rects[tspRevEnd->rt_tidx].xll - bri->rects[tspi->rt_tidx].xur; }
+ tspj->ykern = (bri->rects[tspRevEnd->rt_tidx].yll - tspRevEnd->boff) -
+ (bri->rects[ tspi->rt_tidx].yll - tspi->boff );
+ }
+ }
+ else {
+ if(ldir == LDIR_RL){ tspj->xkern = bri->rects[tspj->rt_tidx].xur - bri->rects[tspi->rt_tidx].xll; }
+ else { tspj->xkern = bri->rects[tspj->rt_tidx].xll - bri->rects[tspi->rt_tidx].xur; }
+ tspj->ykern = (bri->rects[tspj->rt_tidx].yll - tspj->boff) -
+ (bri->rects[tspi->rt_tidx].yll - tspi->boff);
+ }
+
+
+ /*
+ Sometimes a font substitution was absolutely terrible, for instance, for Arial Narrow on (most) Linux systems,
+ The resulting advance (xkern) may be much too large so that it overruns the next text chunk. Since
+ overlapping text on the same line is almost never encountered, this may be used to detect the bad
+ substitution so that a more appropriate offset can be used.
+ Detect this situation as a negative dx < 1/2 a space character's width while |dy| < an entire space width.
+ The y constraints allow super and subscripts, which overlap in x but are shifted above/below in y.
+ */
+ spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0;
+ qsp = 0.25 * spcadv;
+ dx = tspj->xkern;
+ dy = tspj->ykern;
+ if(dy <=qsp && dy >= -qsp){
+ if(ldir==LDIR_RL){
+ if(dx > 2*qsp)tspj->xkern = 0.0;
+ }
+ else {
+ if(dx < -2*qsp)tspj->xkern = 0.0;
+ }
+ }
+
+ /* if x or y kern is less than twice the quantization error it is probably noise, set it to zero */
+ if(fabs(tspj->xkern) <= 2.0*tri->qe)tspj->xkern = 0.0;
+ if(fabs(tspj->ykern) <= 2.0*tri->qe)tspj->ykern = 0.0;
+
+ /* reintroduce spaces on the leading edge of text "j" if the kerning can be in part or in whole replaced
+ with 1 or 2 spaces */
+ if(tspj->ykern == 0.0){
+ double spaces = tspj->xkern/spcadv; /* negative on RL language, positive on LR */
+ if((ldir == LDIR_RL && (spaces <= -0.9 && spaces >= -2.1)) ||
+ (ldir == LDIR_LR && (spaces >= 0.9 && spaces <= 2.1)) ){
+ int ispaces = lround(spaces);
+ tspj->xkern -= ((double)ispaces*spcadv);
+ if(ispaces<0)ispaces=-ispaces;
+ size_t slen = strlen((char *)tspj->string);
+ uint8_t *newstring = malloc(1 + ispaces + slen);
+ sprintf((char *)newstring," "); /* start with two spaces, possibly overwrite one in the next line */
+ memcpy(newstring+ispaces,tspj->string,slen+1); /* copy existing string to proper position */
+ free(tspj->string);
+ tspj->string = newstring;
+ tspj->spaces = ispaces; // only needed to fix optional debugging SVG output later
+ }
+ }
+
+ tspi = tspj;
+ lastldir = ldir;
+ }
+ }
+
+
+ /* Phase 2, try to group sequential lines. There may be "lines" that are still TR_TEXT, as in:
+
+ ... this is a sentence that wraps by one
+ word.
+
+ And some paragrahs might be single word lines (+ = bullet in the following)
+
+ +verbs
+ +nouns
+ +adjectives
+
+ Everything starts out as TR_PARA_UJ and if the next one can be lined up, the type changes to
+ an aligned paragraph and complexes are appended to the existing one.
+ */
+
+ for(i=0; i < cxi->phase1; i++){
+ type = TR_PARA_UJ; /* any paragraph alignment will be acceptable */
+ /* Must make a copy as next call may reallocate rects, so if we just passed a pointer to something in the structure
+ it would vaporize part way through the call. */
+ memcpy(&bsp,&(bri->rects[cxi->cx[i].rt_cidx]),sizeof(BRECT_SPECS));
+ (void) brinfo_insert(bri,&bsp);
+ dst_rt = bri->used-1;
+ (void) cxinfo_insert(cxi, i, dst_rt, type);
+
+ cxi->paras++;
+ ok = 1;
+ for(j=i+1; ok && (j < cxi->phase1); j++){
+ type = brinfo_pp_alignment(bri, cxi->cx[i].rt_cidx, cxi->cx[j].rt_cidx, 3*tri->qe, type);
+ switch (type){
+ case TR_PARA_UJ: /* paragraph type was set and j line does not fit, or no paragraph alignment matched */
+ ok = 0; /* force exit from j loop */
+ j--; /* this will increment at loop bottom */
+ break;
+ case TR_PARA_LJ:
+ case TR_PARA_CJ:
+ case TR_PARA_RJ:
+ /* two successive lines have been identified (possible following others already in the paragraph */
+ if(TR_check_set_vadvance(tri,j,i)){ /* check for compatibility with vadvance if set, set it if it isn't. */
+ ok = 0; /* force exit from j loop */
+ j--; /* this will increment at loop bottom */
+ }
+ else {
+ src_rt = cxi->cx[j].rt_cidx;
+ (void) cxinfo_append(cxi, j, type);
+ (void) brinfo_merge(bri, dst_rt, src_rt);
+ }
+ break;
+ default:
+ return(-6); /* programming error */
+ }
+ }
+ if(j>=cxi->phase1)break;
+ i=j-1;
+ }
+
+
+/* When debugging
+ cxinfo_dump(tri);
+*/
+
+ return(cxi->used);
+}
+
+
+/* no doxygen documentation below this point, these pieces are for the text program, not the library. */
+
+#if TEST
+#define MAXLINE 2048 /* big enough for testing */
+enum OP_TYPES {OPCOM,OPOOPS,OPFONT,OPESC,OPORI,OPXY,OPFS,OPTEXT,OPALN,OPLDIR,OPMUL,OPITA,OPWGT,OPDEC,OPCND,OPBKG,OPCLR,OPDCLR,OPBCLR,OPFLAGS,OPEMIT,OPDONE};
+
+int parseit(char *buffer,char **data){
+ int pre;
+ pre = strcspn(buffer,":");
+ if(!pre)return(OPOOPS);
+ *data=&buffer[pre+1];
+ buffer[pre]='\0';
+ if(*buffer=='#' )return(OPCOM );
+ if(0==strcmp("FONT",buffer))return(OPFONT);
+ if(0==strcmp("ESC" ,buffer))return(OPESC );
+ if(0==strcmp("ORI", buffer))return(OPORI );
+ if(0==strcmp("XY", buffer))return(OPXY );
+ if(0==strcmp("FS", buffer))return(OPFS );
+ if(0==strcmp("TEXT",buffer))return(OPTEXT);
+ if(0==strcmp("ALN", buffer))return(OPALN );
+ if(0==strcmp("LDIR",buffer))return(OPLDIR);
+ if(0==strcmp("MUL", buffer))return(OPMUL );
+ if(0==strcmp("ITA", buffer))return(OPITA );
+ if(0==strcmp("WGT", buffer))return(OPWGT );
+ if(0==strcmp("DEC", buffer))return(OPDEC );
+ if(0==strcmp("CND", buffer))return(OPCND );
+ if(0==strcmp("BKG", buffer))return(OPBKG );
+ if(0==strcmp("CLR", buffer))return(OPCLR );
+ if(0==strcmp("DCLR", buffer))return(OPDCLR );
+ if(0==strcmp("BCLR",buffer))return(OPBCLR );
+ if(0==strcmp("FLAG",buffer))return(OPFLAGS);
+ if(0==strcmp("EMIT",buffer))return(OPEMIT);
+ if(0==strcmp("DONE",buffer))return(OPDONE);
+ return(OPOOPS);
+}
+
+void boom(char *string,int lineno){
+ fprintf(stderr,"Fatal error at line %d %s\n",lineno,string);
+ exit(EXIT_FAILURE);
+}
+
+
+void init_as_svg(TR_INFO *tri){
+ TRPRINT(tri,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
+ TRPRINT(tri,"<!-- Created with Inkscape (http://www.inkscape.org/) -->\n");
+ TRPRINT(tri,"\n");
+ TRPRINT(tri,"<svg\n");
+ TRPRINT(tri," xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n");
+ TRPRINT(tri," xmlns:cc=\"http://creativecommons.org/ns#\"\n");
+ TRPRINT(tri," xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n");
+ TRPRINT(tri," xmlns:svg=\"http://www.w3.org/2000/svg\"\n");
+ TRPRINT(tri," xmlns=\"http://www.w3.org/2000/svg\"\n");
+ TRPRINT(tri," xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n");
+ TRPRINT(tri," xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"\n");
+ TRPRINT(tri," width=\"900\"\n");
+ TRPRINT(tri," height=\"675\"\n");
+ TRPRINT(tri," id=\"svg4122\"\n");
+ TRPRINT(tri," version=\"1.1\"\n");
+ TRPRINT(tri," inkscape:version=\"0.48+devel r11679 custom\"\n");
+ TRPRINT(tri," sodipodi:docname=\"simplest_text.svg\">\n");
+ TRPRINT(tri," <defs\n");
+ TRPRINT(tri," id=\"defs4124\" />\n");
+ TRPRINT(tri," <sodipodi:namedview\n");
+ TRPRINT(tri," id=\"base\"\n");
+ TRPRINT(tri," pagecolor=\"#ffffff\"\n");
+ TRPRINT(tri," bordercolor=\"#666666\"\n");
+ TRPRINT(tri," borderopacity=\"1.0\"\n");
+ TRPRINT(tri," inkscape:pageopacity=\"0.0\"\n");
+ TRPRINT(tri," inkscape:pageshadow=\"2\"\n");
+ TRPRINT(tri," inkscape:zoom=\"0.98994949\"\n");
+ TRPRINT(tri," inkscape:cx=\"309.88761\"\n");
+ TRPRINT(tri," inkscape:cy=\"482.63995\"\n");
+ TRPRINT(tri," inkscape:document-units=\"px\"\n");
+ TRPRINT(tri," inkscape:current-layer=\"layer1\"\n");
+ TRPRINT(tri," showgrid=\"false\"\n");
+ TRPRINT(tri," width=\"0px\"\n");
+ TRPRINT(tri," height=\"0px\"\n");
+ TRPRINT(tri," fit-margin-top=\"0\"\n");
+ TRPRINT(tri," fit-margin-left=\"0\"\n");
+ TRPRINT(tri," fit-margin-right=\"0\"\n");
+ TRPRINT(tri," fit-margin-bottom=\"0\"\n");
+ TRPRINT(tri," units=\"in\"\n");
+ TRPRINT(tri," inkscape:window-width=\"1200\"\n");
+ TRPRINT(tri," inkscape:window-height=\"675\"\n");
+ TRPRINT(tri," inkscape:window-x=\"26\"\n");
+ TRPRINT(tri," inkscape:window-y=\"51\"\n");
+ TRPRINT(tri," inkscape:window-maximized=\"0\" />\n");
+ TRPRINT(tri," <metadata\n");
+ TRPRINT(tri," id=\"metadata4127\">\n");
+ TRPRINT(tri," <rdf:RDF>\n");
+ TRPRINT(tri," <cc:Work\n");
+ TRPRINT(tri," rdf:about=\"\">\n");
+ TRPRINT(tri," <dc:format>image/svg+xml</dc:format>\n");
+ TRPRINT(tri," <dc:type\n");
+ TRPRINT(tri," rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" />\n");
+ TRPRINT(tri," <dc:title></dc:title>\n");
+ TRPRINT(tri," </cc:Work>\n");
+ TRPRINT(tri," </rdf:RDF>\n");
+ TRPRINT(tri," </metadata>\n");
+ TRPRINT(tri," <g\n");
+ TRPRINT(tri," inkscape:label=\"Layer 1\"\n");
+ TRPRINT(tri," inkscape:groupmode=\"layer\"\n");
+ TRPRINT(tri," id=\"layer1\"\n");
+ TRPRINT(tri," transform=\"translate(0,0)\">\n");
+ TRPRINT(tri,"\n");
+}
+
+
+void flush_as_svg(TR_INFO *tri, FILE *fp){
+ fwrite(tri->out,tri->outused,1,fp);
+}
+
+FILE *close_as_svg(TR_INFO *tri, FILE *fp){
+ TRPRINT(tri, " </g>\n");
+ TRPRINT(tri, "</svg>\n");
+ flush_as_svg(tri,fp);
+ fclose(fp);
+ return(NULL);
+}
+
+
+int main(int argc, char *argv[]){
+ char *data;
+ char inbuf[MAXLINE];
+ FILE *fpi = NULL;
+ FILE *fpo = NULL;
+ int op;
+ double fact = 1.0; /* input units to points */
+ double escapement = 0.0; /* degrees */
+ int lineno = 0;
+ int ok = 1;
+ int status;
+ TCHUNK_SPECS tsp;
+ TR_INFO *tri=NULL;
+ int flags=0;
+ char *infile;
+ uint32_t utmp32;
+ TRCOLORREF bkcolor;
+ int bkmode;
+ char *fontspec;
+
+ infile=malloc(strlen(argv[1])+1);
+ strcpy(infile,argv[1]);
+
+ if(argc < 2 || !(fpi = fopen(infile,"r"))){
+ printf("Usage: text_reassemble input_file\n");
+ printf(" Test program reads an input file containing lines like:\n");
+ printf(" FONT:(font for next text)\n");
+ printf(" ESC:(escapement angle degrees of text line, up from X axis)\n");
+ printf(" ORI:(angle degrees of character orientation, up from X axis)\n");
+ printf(" FS:(font size, units)\n");
+ printf(" XY:(x,y) X 0 is at left, N is at right, Y 0 is at top, N is at bottom, as page is viewed.\n");
+ printf(" TEXT:(UTF8 text)\n");
+ printf(" ALN:combination of {LCR}{BLT} = Text is placed on {X,Y} at Left/Center/Right of text, at Bottom,baseLine,Top of text.\n");
+ printf(" LDIR:{LR|RL|TB) Left to Right, Right to Left, and Top to Bottom \n");
+ printf(" MUL:(float, multiplicative factor to convert FS,XY units to points).\n");
+ printf(" ITA:(Italics, 0=normal, 100=italics, 110=oblique).\n");
+ printf(" WGT:(Weight, 0-215: 80=normal, 200=bold, 215=ultrablack, 0=thin)).\n");
+ printf(" DEC:(this is a bit field. For color see DCLR\n");
+ printf(" style: 000 none, 001 underline,002 overline, 004 blink, 008 strike-through\n");
+ printf(" line: 000 solid, 010 double, 020 dotted, 040 dashed, 080 wavy)\n");
+ printf(" CND:(Condensed 50-200: 100=normal, 50=ultracondensed, 75=condensed, 200=expanded).\n");
+ printf(" BKG:(Background color: 0 none, 1 by input fragment, 2 by assembled line, 3 by entire assembly. Use BCLR, THEN BKG) \n");
+ printf(" CLR:(Text RGB color, as 6 HEX digits, like: FF0000 (red) or 0000FF (blue)) \n");
+ printf(" DCLR:(Decoration color, specify like CLR, except 1000000 or higher disables.)\n");
+ printf(" BCLR:(Background RGB color, specify like CLR.) \n");
+ printf(" FLAG: Special processing options. 1 EMF compatible text alignment.\n");
+ printf(" EMIT:(Process everything up to this point, then start clean for remaining input).\n");
+ printf(" DONE:(no more input, process it).\n");
+ printf(" # comment\n");
+ printf("\n");
+ printf(" The output is a summary of how the pieces are to be assembled into complex text.\n");
+ printf("\n");
+ printf(" egrep pattern: '^LOAD:|^FONT:|^ESC:|^ORI:|^FS:|^XY:|^TEXT:|^ALN:|^LDIR:|^MUL:|^ITA:|^WGT:|^DEC:|^CND:|^BKG:|^CLR:|^BCLR:|^DCLR:|^FLAG:|^EMIT:^DONE:'\n");
+ exit(EXIT_FAILURE);
+ }
+
+ tri = trinfo_init(tri); /* If it loops the trinfo_clear at the end will reset tri to the proper state, do NOT call trinfo_init twice! */
+
+#ifdef DBG_LOOP
+ int ldx;
+ for(ldx=0;ldx<5;ldx++){
+ if(fpi)fclose(fpi);
+ fpi = fopen(infile,"r");
+#endif
+ tsp.string = NULL;
+ tsp.ori = 0.0; /* degrees */
+ tsp.fs = 12.0; /* font size */
+ tsp.x = 0.0;
+ tsp.y = 0.0;
+ tsp.boff = 0.0; /* offset to baseline from LL corner of bounding rectangle, changes with fs and taln*/
+ tsp.vadvance = 0.0; /* meaningful only when a complex contains two or more lines */
+ tsp.taln = ALILEFT + ALIBASE;
+ tsp.ldir = LDIR_LR;
+ tsp.color.Red = tsp.decColor.Red = 0; /* RGBA Black */
+ tsp.color.Green = tsp.decColor.Green = 0; /* RGBA Black */
+ tsp.color.Blue = tsp.decColor.Blue = 0; /* RGBA Black */
+ tsp.color.Reserved = tsp.decColor.Reserved = 0; /* unused */
+ tsp.italics = 0;
+ tsp.weight = 80;
+ tsp.condensed = 100;
+ tsp.decoration = 0; /* none */
+ tsp.spaces = 0; /* none */
+ tsp.fi_idx = -1; /* set to an invalid */
+ tsp.rt_tidx = -1; /* set to an invalid */
+ tsp.xkern = tsp.ykern = 0.0;
+ /* no need to set rt_tidx */
+
+
+
+ if(!tri){
+ fprintf(stderr,"Fatal error, could not initialize data structures\n");
+ exit(EXIT_FAILURE);
+ }
+ (void) trinfo_load_ft_opts(tri, 1,
+ FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP,
+ FT_KERNING_UNSCALED);
+
+ fpo=fopen("dump.svg","wb");
+ init_as_svg(tri);
+
+ while(ok){
+ lineno++;
+ if(!fgets(inbuf,MAXLINE,fpi))boom("Unexpected end of file - no DONE:",lineno);
+ inbuf[strlen(inbuf)-1]='\0'; /* step on the EOL character */
+ op = parseit(inbuf,&data);
+ switch(op){
+ case OPCOM: /* ignore comments*/
+ break;
+ case OPFONT:
+ /* If the font name includes "Narrow" condensed may not have been set */
+ if(0<= TR_findcasesub(data, "Narrow")){
+ tsp.co=1;
+ }
+ else {
+ tsp.co=0;
+ }
+ fontspec = TR_construct_fontspec(&tsp, data);
+ if((tsp.fi_idx = ftinfo_load_fontname(tri->fti, fontspec)) < 0 )boom("Font load failed",lineno);
+ free(fontspec);
+ break;
+ case OPESC:
+ if(1 != sscanf(data,"%lf",&escapement))boom("Invalid ESC:",lineno);
+ break;
+ case OPORI:
+ if(1 != sscanf(data,"%lf",&tsp.ori))boom("Invalid ORI:",lineno);
+ break;
+ case OPFS:
+ if(1 != sscanf(data,"%lf",&tsp.fs) || tsp.fs <= 0.0)boom("Invalid FS:",lineno);
+ tsp.fs *= fact;
+ break;
+ case OPXY:
+ if(2 != sscanf(data,"%lf,%lf",&tsp.x,&tsp.y) )boom("Invalid XY:",lineno);
+ tsp.x *= fact;
+ tsp.y *= fact;
+ break;
+ case OPTEXT:
+ tsp.string = (uint8_t *) U_strdup(data);
+ /* FreeType parameters match inkscape*/
+ status = trinfo_load_textrec(tri, &tsp, escapement,flags);
+ if(status==-1){ // change of escapement, emit what we have and reset
+ TR_layout_analyze(tri);
+ TR_layout_2_svg(tri);
+ flush_as_svg(tri, fpo);
+ tri = trinfo_clear(tri);
+ if(trinfo_load_textrec(tri, &tsp, escapement,flags)){ boom("Text load failed",lineno); }
+ }
+ else if(status){ boom("Text load failed",lineno); }
+ break;
+ case OPALN:
+ tsp.taln=0;
+ switch (*data++){
+ case 'L': tsp.taln |= ALILEFT; break;
+ case 'C': tsp.taln |= ALICENTER; break;
+ case 'R': tsp.taln |= ALIRIGHT; break;
+ default: boom("Invalid ALN:",lineno);
+ }
+ switch (*data++){
+ case 'T': tsp.taln |= ALITOP; break;
+ case 'L': tsp.taln |= ALIBASE; break;
+ case 'B': tsp.taln |= ALIBOT; break;
+ default: boom("Invalid ALN:",lineno);
+ }
+ break;
+ case OPLDIR:
+ tsp.ldir=0;
+ if(0==strcmp("LR",data)){ tsp.ldir=LDIR_LR; break;}
+ if(0==strcmp("RL",data)){ tsp.ldir=LDIR_RL; break;}
+ if(0==strcmp("TB",data)){ tsp.ldir=LDIR_TB; break;}
+ boom("Invalid LDIR:",lineno);
+ break;
+ case OPMUL:
+ if(1 != sscanf(data,"%lf",&fact) || fact <= 0.0)boom("Invalid MUL:",lineno);
+ (void) trinfo_load_qe(tri,fact);
+ break;
+ case OPITA:
+ if(1 != sscanf(data,"%d",&tsp.italics) || tsp.italics < 0 || tsp.italics>110)boom("Invalid ITA:",lineno);
+ break;
+ case OPWGT:
+ if(1 != sscanf(data,"%d",&tsp.weight) || tsp.weight < 0 || tsp.weight > 215)boom("Invalid WGT:",lineno);
+ break;
+ case OPDEC:
+ if(1 != sscanf(data,"%X",(unsigned int *) &tsp.decoration))boom("Invalid DEC:",lineno);
+ break;
+ case OPCND:
+ if(1 != sscanf(data,"%d",&tsp.condensed) || tsp.condensed < 50 || tsp.condensed > 200)boom("Invalid CND:",lineno);
+ break;
+ case OPBKG:
+ if(1 != sscanf(data,"%d",&bkmode) )boom("Invalid BKG:",lineno);
+ (void) trinfo_load_bk(tri,bkmode,bkcolor);
+ break;
+ case OPCLR:
+ if(1 != sscanf(data,"%x",&utmp32) )boom("Invalid CLR:",lineno);
+ tsp.color.Red = (utmp32 >> 16) & 0xFF;
+ tsp.color.Green = (utmp32 >> 8) & 0xFF;
+ tsp.color.Blue = (utmp32 >> 0) & 0xFF;
+ tsp.color.Reserved = 0;
+ break;
+ case OPDCLR:
+ if(1 != sscanf(data,"%x",&utmp32) )boom("Invalid DCLR:",lineno);
+ if(utmp32 >= 0x1000000){
+ tsp.decColor.Red = tsp.decColor.Green = tsp.decColor.Blue = tsp.decColor.Reserved = 0;
+ tsp.decoration &= ~TXTDECOR_CLRSET;
+ }
+ else {
+ tsp.decColor.Red = (utmp32 >> 16) & 0xFF;
+ tsp.decColor.Green = (utmp32 >> 8) & 0xFF;
+ tsp.decColor.Blue = (utmp32 >> 0) & 0xFF;
+ tsp.decColor.Reserved = 0;
+ tsp.decoration |= TXTDECOR_CLRSET;
+ }
+ break;
+ case OPBCLR:
+ if(1 != sscanf(data,"%x",&utmp32) )boom("Invalid BCLR:",lineno);
+ bkcolor.Red = (utmp32 >> 16) & 0xFF;
+ bkcolor.Green = (utmp32 >> 8) & 0xFF;
+ bkcolor.Blue = (utmp32 >> 0) & 0xFF;
+ bkcolor.Reserved = 0;
+ break;
+ case OPFLAGS:
+ if(1 != sscanf(data,"%d",&flags) )boom("Invalid FLAG:",lineno);
+ break;
+ case OPEMIT:
+ TR_layout_analyze(tri);
+ TR_layout_2_svg(tri);
+ flush_as_svg(tri, fpo);
+ tri = trinfo_clear(tri);
+ break;
+ case OPDONE:
+ TR_layout_analyze(tri);
+ TR_layout_2_svg(tri);
+ flush_as_svg(tri, fpo);
+ tri = trinfo_clear(tri);
+ ok = 0;
+ break;
+ case OPOOPS:
+ default:
+ boom("Input line cannot be parsed",lineno);
+ break;
+ }
+
+ }
+
+ if(fpo){
+ fpo=close_as_svg(tri, fpo);
+ }
+
+
+#ifdef DBG_LOOP
+ tri = trinfo_clear(tri);
+ ok = 1;
+ }
+#endif /* DBG_LOOP */
+
+ fclose(fpi);
+ tri = trinfo_release(tri);
+ free(infile);
+
+ exit(EXIT_SUCCESS);
+}
+#endif /* TEST */
+
+#ifdef __cplusplus
+}
+#endif