Merge pull request #3121 from harfbuzz/subset-args

Fix up `hb-subset --help-all`
This commit is contained in:
Behdad Esfahbod 2021-08-09 13:21:07 -06:00 committed by GitHub
commit 8940409e3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 3241 additions and 3320 deletions

View File

@ -1,38 +1,48 @@
HB_VIEW_sources = \
hb-view.cc \
options.cc \
options.hh \
main-font-text.hh \
shape-consumer.hh \
ansi-print.cc \
ansi-print.hh \
helper-cairo.cc \
helper-cairo.hh \
helper-cairo-ansi.cc \
face-options.hh \
font-options.hh \
hb-view.cc \
helper-cairo-ansi.hh \
view-cairo.cc \
helper-cairo.hh \
main-font-text.hh \
options.hh \
output-options.hh \
shape-consumer.hh \
shape-options.hh \
text-options.hh \
view-cairo.hh \
view-options.hh \
$(NULL)
HB_SHAPE_sources = \
face-options.hh \
font-options.hh \
hb-shape.cc \
options.cc \
options.hh \
main-font-text.hh \
options.hh \
output-options.hh \
shape-consumer.hh \
$(NULL)
HB_OT_SHAPE_CLOSURE_sources = \
hb-ot-shape-closure.cc \
options.cc \
options.hh \
main-font-text.hh \
shape-format.hh \
shape-options.hh \
text-options.hh \
$(NULL)
HB_SUBSET_CLI_sources = \
face-options.hh \
hb-subset.cc \
options.cc \
options-subset.cc \
options.hh \
main-font-text.hh \
options.hh \
output-options.hh \
subset-options.hh \
text-options.hh \
$(NULL)
HB_OT_SHAPE_CLOSURE_sources = \
face-options.hh \
font-options.hh \
hb-ot-shape-closure.cc \
main-font-text.hh \
options.hh \
text-options.hh \
$(NULL)

View File

@ -1,427 +0,0 @@
/*
* Copyright © 2012 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Google Author(s): Behdad Esfahbod
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "ansi-print.hh"
#include <assert.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <fcntl.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* for isatty() */
#endif
#if defined (_MSC_VER) && (_MSC_VER < 1800)
static inline long int
lround (double x)
{
if (x >= 0)
return floor (x + 0.5);
else
return ceil (x - 0.5);
}
#endif
#define ESC_E (char)27
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define CELL_W 8
#define CELL_H (2 * CELL_W)
struct color_diff_t
{
int dot (const color_diff_t &o)
{ return v[0]*o.v[0] + v[1]*o.v[1] + v[2]*o.v[2] + v[3]*o.v[3]; }
int v[4];
};
struct color_t
{
static color_t from_ansi (unsigned int x)
{
color_t c = {(0xFFu<<24) | ((0xFFu*(x&1))<<16) | ((0xFFu*((x >> 1)&1))<<8) | (0xFFu*((x >> 2)&1))};
return c;
}
unsigned int to_ansi ()
{
return ((v >> 23) & 1) | ((v >> 14)&2) | ((v >> 5)&4);
}
color_diff_t diff (const color_t &o)
{
color_diff_t d;
for (unsigned int i = 0; i < 4; i++)
d.v[i] = (int) ((v >> (i*8))&0xFF) - (int) ((o.v >> (i*8))&0xFF);
return d;
}
uint32_t v;
};
struct image_t
{
public:
image_t (unsigned int width_,
unsigned int height_,
const uint32_t *data_,
unsigned int stride_) :
width (width_),
height (height_),
own_data (false),
data ((color_t *) data_),
stride (stride_) {}
image_t (unsigned int width_,
unsigned int height_) :
width (width_),
height (height_),
own_data (true),
data ((color_t *) malloc (sizeof (data[0]) * width * height)),
stride (width) {}
~image_t ()
{ if (own_data) free (data); }
color_t &operator () (unsigned int x, unsigned int y)
{ return data[x + y * stride]; }
color_t operator () (unsigned int x, unsigned int y) const
{ return data[x + y * stride]; }
void
copy_sub_image (const image_t &s,
unsigned int x, unsigned int y,
unsigned int w, unsigned int h)
{
assert (x < width);
assert (y < height);
for (unsigned int row = 0; row < h; row++) {
color_t *p = data + x + MIN (y + row, height - 1) * stride;
color_t *q = s.data + row * s.stride;
if (x + w <= width)
for (unsigned int col = 0; col < w; col++)
*q++ = *p++;
else {
unsigned int limit = width - x;
for (unsigned int col = 0; col < limit; col++)
*q++ = *p++;
p--;
for (unsigned int col = limit; col < w; col++)
*q++ = *p;
}
}
}
const unsigned int width;
const unsigned int height;
private:
bool own_data;
color_t * const data;
const unsigned int stride;
};
struct biimage_t
{
public:
biimage_t (unsigned int width, unsigned int height) :
width (width),
height (height),
bg (0), fg (0), unicolor (true),
data ((uint8_t *) malloc (sizeof (data[0]) * width * height)) {}
~biimage_t ()
{ free (data); }
void set (const image_t &image)
{
assert (image.width == width);
assert (image.height == height);
int freq[8] = {0};
for (unsigned int y = 0; y < height; y++)
for (unsigned int x = 0; x < width; x++) {
color_t c = image (x, y);
freq[c.to_ansi ()]++;
}
bg = 0;
for (unsigned int i = 1; i < 8; i++)
if (freq[bg] < freq[i])
bg = i;
fg = 0;
for (unsigned int i = 1; i < 8; i++)
if (i != bg && freq[fg] < freq[i])
fg = i;
if (fg == bg || freq[fg] == 0) {
fg = bg;
unicolor = true;
}
else
unicolor = false;
/* Set the data... */
if (unicolor) {
memset (data, 0, sizeof (data[0]) * width * height);
return;
}
color_t bgc = color_t::from_ansi (bg);
color_t fgc = color_t::from_ansi (fg);
color_diff_t diff = fgc.diff (bgc);
int dd = diff.dot (diff);
for (unsigned int y = 0; y < height; y++)
for (unsigned int x = 0; x < width; x++) {
int d = diff.dot (image (x, y).diff (bgc));
(*this)(x, y) = d < 0 ? 0 : d > dd ? 255 : lround (d * 255. / dd);
}
}
uint8_t &operator () (unsigned int x, unsigned int y)
{ return data[x + y * width]; }
uint8_t operator () (unsigned int x, unsigned int y) const
{ return data[x + y * width]; }
const unsigned int width;
const unsigned int height;
unsigned int bg;
unsigned int fg;
bool unicolor;
private:
uint8_t * const data;
};
static const char *
block_best (const biimage_t &bi, bool *inverse)
{
assert (bi.width <= CELL_W);
assert (bi.height <= CELL_H);
unsigned int score = UINT_MAX;
unsigned int row_sum[CELL_H] = {0};
unsigned int col_sum[CELL_W] = {0};
unsigned int row_sum_i[CELL_H] = {0};
unsigned int col_sum_i[CELL_W] = {0};
unsigned int quad[2][2] = {{0}};
unsigned int quad_i[2][2] = {{0}};
unsigned int total = 0;
unsigned int total_i = 0;
for (unsigned int y = 0; y < bi.height; y++)
for (unsigned int x = 0; x < bi.width; x++) {
unsigned int c = bi (x, y);
unsigned int c_i = 255 - c;
row_sum[y] += c;
row_sum_i[y] += c_i;
col_sum[x] += c;
col_sum_i[x] += c_i;
quad[2 * y / bi.height][2 * x / bi.width] += c;
quad_i[2 * y / bi.height][2 * x / bi.width] += c_i;
total += c;
total_i += c_i;
}
/* Make the sums cummulative */
for (unsigned int i = 1; i < bi.height; i++) {
row_sum[i] += row_sum[i - 1];
row_sum_i[i] += row_sum_i[i - 1];
}
for (unsigned int i = 1; i < bi.width; i++) {
col_sum[i] += col_sum[i - 1];
col_sum_i[i] += col_sum_i[i - 1];
}
const char *best_c = " ";
/* Maybe empty is better! */
if (total < score) {
score = total;
*inverse = false;
best_c = " ";
}
/* Maybe full is better! */
if (total_i < score) {
score = total_i;
*inverse = true;
best_c = " ";
}
/* Find best lower line */
if (1) {
unsigned int best_s = UINT_MAX;
bool best_inv = false;
int best_i = 0;
for (unsigned int i = 0; i < bi.height - 1; i++)
{
unsigned int s;
s = row_sum[i] + total_i - row_sum_i[i];
if (s < best_s) {
best_s = s;
best_i = i;
best_inv = false;
}
s = row_sum_i[i] + total - row_sum[i];
if (s < best_s) {
best_s = s;
best_i = i;
best_inv = true;
}
}
if (best_s < score) {
static const char *lower[7] = {"", "", "", "", "", "", ""};
unsigned int which = lround ((double) ((best_i + 1) * 8) / bi.height);
if (1 <= which && which <= 7) {
score = best_s;
*inverse = best_inv;
best_c = lower[7 - which];
}
}
}
/* Find best left line */
if (1) {
unsigned int best_s = UINT_MAX;
bool best_inv = false;
int best_i = 0;
for (unsigned int i = 0; i < bi.width - 1; i++)
{
unsigned int s;
s = col_sum[i] + total_i - col_sum_i[i];
if (s < best_s) {
best_s = s;
best_i = i;
best_inv = true;
}
s = col_sum_i[i] + total - col_sum[i];
if (s < best_s) {
best_s = s;
best_i = i;
best_inv = false;
}
}
if (best_s < score) {
static const char *left [7] = {"", "", "", "", "", "", ""};
unsigned int which = lround ((double) ((best_i + 1) * 8) / bi.width);
if (1 <= which && which <= 7) {
score = best_s;
*inverse = best_inv;
best_c = left[which - 1];
}
}
}
/* Find best quadrant */
if (1) {
unsigned int q = 0;
unsigned int qs = 0;
for (unsigned int i = 0; i < 2; i++)
for (unsigned int j = 0; j < 2; j++)
if (quad[i][j] > quad_i[i][j]) {
q += 1 << (2 * i + j);
qs += quad_i[i][j];
} else
qs += quad[i][j];
if (qs < score) {
const char *c = nullptr;
bool inv = false;
switch (q) {
case 1: c = ""; inv = true; break;
case 2: c = ""; inv = true; break;
case 4: c = ""; inv = false; break;
case 8: c = ""; inv = false; break;
case 9: c = ""; inv = false; break;
case 6: c = ""; inv = false; break;
case 7: c = ""; inv = true; break;
case 11: c = ""; inv = true; break;
case 13: c = ""; inv = true; break;
case 14: c = ""; inv = true; break;
}
if (c) {
score = qs;
*inverse = inv;
best_c = c;
}
}
}
return best_c;
}
void
ansi_print_image_rgb24 (const uint32_t *data,
unsigned int width,
unsigned int height,
unsigned int stride)
{
image_t image (width, height, data, stride);
unsigned int rows = (height + CELL_H - 1) / CELL_H;
unsigned int cols = (width + CELL_W - 1) / CELL_W;
image_t cell (CELL_W, CELL_H);
biimage_t bi (CELL_W, CELL_H);
unsigned int last_bg = -1, last_fg = -1;
for (unsigned int row = 0; row < rows; row++) {
for (unsigned int col = 0; col < cols; col++) {
image.copy_sub_image (cell, col * CELL_W, row * CELL_H, CELL_W, CELL_H);
bi.set (cell);
if (bi.unicolor) {
if (last_bg != bi.bg) {
printf ("%c[%dm", ESC_E, 40 + bi.bg);
last_bg = bi.bg;
}
printf (" ");
} else {
/* Figure out the closest character to the biimage */
bool inverse = false;
const char *c = block_best (bi, &inverse);
if (inverse) {
if (last_bg != bi.fg || last_fg != bi.bg) {
printf ("%c[%d;%dm", ESC_E, 30 + bi.bg, 40 + bi.fg);
last_bg = bi.fg;
last_fg = bi.bg;
}
} else {
if (last_bg != bi.bg || last_fg != bi.fg) {
printf ("%c[%d;%dm", ESC_E, 40 + bi.bg, 30 + bi.fg);
last_bg = bi.bg;
last_fg = bi.fg;
}
}
printf ("%s", c);
}
}
printf ("%c[0m\n", ESC_E); /* Reset */
last_bg = last_fg = -1;
}
}

View File

@ -29,11 +29,398 @@
#include "hb.hh"
void
#include <assert.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <fcntl.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* for isatty() */
#endif
#if defined (_MSC_VER) && (_MSC_VER < 1800)
static inline long int
lround (double x)
{
if (x >= 0)
return floor (x + 0.5);
else
return ceil (x - 0.5);
}
#endif
#define ESC_E (char)27
#define CELL_W 8
#define CELL_H (2 * CELL_W)
struct color_diff_t
{
int dot (const color_diff_t &o)
{ return v[0]*o.v[0] + v[1]*o.v[1] + v[2]*o.v[2] + v[3]*o.v[3]; }
int v[4];
};
struct color_t
{
static color_t from_ansi (unsigned int x)
{
color_t c = {(0xFFu<<24) | ((0xFFu*(x&1))<<16) | ((0xFFu*((x >> 1)&1))<<8) | (0xFFu*((x >> 2)&1))};
return c;
}
unsigned int to_ansi ()
{
return ((v >> 23) & 1) | ((v >> 14)&2) | ((v >> 5)&4);
}
color_diff_t diff (const color_t &o)
{
color_diff_t d;
for (unsigned int i = 0; i < 4; i++)
d.v[i] = (int) ((v >> (i*8))&0xFF) - (int) ((o.v >> (i*8))&0xFF);
return d;
}
uint32_t v;
};
struct image_t
{
public:
image_t (unsigned int width_,
unsigned int height_,
const uint32_t *data_,
unsigned int stride_) :
width (width_),
height (height_),
own_data (false),
data ((color_t *) data_),
stride (stride_) {}
image_t (unsigned int width_,
unsigned int height_) :
width (width_),
height (height_),
own_data (true),
data ((color_t *) malloc (sizeof (data[0]) * width * height)),
stride (width) {}
~image_t ()
{ if (own_data) free (data); }
color_t &operator () (unsigned int x, unsigned int y)
{ return data[x + y * stride]; }
color_t operator () (unsigned int x, unsigned int y) const
{ return data[x + y * stride]; }
void
copy_sub_image (const image_t &s,
unsigned int x, unsigned int y,
unsigned int w, unsigned int h)
{
assert (x < width);
assert (y < height);
for (unsigned int row = 0; row < h; row++) {
color_t *p = data + x + hb_min (y + row, height - 1) * stride;
color_t *q = s.data + row * s.stride;
if (x + w <= width)
for (unsigned int col = 0; col < w; col++)
*q++ = *p++;
else {
unsigned int limit = width - x;
for (unsigned int col = 0; col < limit; col++)
*q++ = *p++;
p--;
for (unsigned int col = limit; col < w; col++)
*q++ = *p;
}
}
}
const unsigned int width;
const unsigned int height;
private:
bool own_data;
color_t * const data;
const unsigned int stride;
};
struct biimage_t
{
public:
biimage_t (unsigned int width, unsigned int height) :
width (width),
height (height),
bg (0), fg (0), unicolor (true),
data ((uint8_t *) malloc (sizeof (data[0]) * width * height)) {}
~biimage_t ()
{ free (data); }
void set (const image_t &image)
{
assert (image.width == width);
assert (image.height == height);
int freq[8] = {0};
for (unsigned int y = 0; y < height; y++)
for (unsigned int x = 0; x < width; x++) {
color_t c = image (x, y);
freq[c.to_ansi ()]++;
}
bg = 0;
for (unsigned int i = 1; i < 8; i++)
if (freq[bg] < freq[i])
bg = i;
fg = 0;
for (unsigned int i = 1; i < 8; i++)
if (i != bg && freq[fg] < freq[i])
fg = i;
if (fg == bg || freq[fg] == 0) {
fg = bg;
unicolor = true;
}
else
unicolor = false;
/* Set the data... */
if (unicolor) {
memset (data, 0, sizeof (data[0]) * width * height);
return;
}
color_t bgc = color_t::from_ansi (bg);
color_t fgc = color_t::from_ansi (fg);
color_diff_t diff = fgc.diff (bgc);
int dd = diff.dot (diff);
for (unsigned int y = 0; y < height; y++)
for (unsigned int x = 0; x < width; x++) {
int d = diff.dot (image (x, y).diff (bgc));
(*this)(x, y) = d < 0 ? 0 : d > dd ? 255 : lround (d * 255. / dd);
}
}
uint8_t &operator () (unsigned int x, unsigned int y)
{ return data[x + y * width]; }
uint8_t operator () (unsigned int x, unsigned int y) const
{ return data[x + y * width]; }
const unsigned int width;
const unsigned int height;
unsigned int bg;
unsigned int fg;
bool unicolor;
private:
uint8_t * const data;
};
static const char *
block_best (const biimage_t &bi, bool *inverse)
{
assert (bi.width <= CELL_W);
assert (bi.height <= CELL_H);
unsigned int score = UINT_MAX;
unsigned int row_sum[CELL_H] = {0};
unsigned int col_sum[CELL_W] = {0};
unsigned int row_sum_i[CELL_H] = {0};
unsigned int col_sum_i[CELL_W] = {0};
unsigned int quad[2][2] = {{0}};
unsigned int quad_i[2][2] = {{0}};
unsigned int total = 0;
unsigned int total_i = 0;
for (unsigned int y = 0; y < bi.height; y++)
for (unsigned int x = 0; x < bi.width; x++) {
unsigned int c = bi (x, y);
unsigned int c_i = 255 - c;
row_sum[y] += c;
row_sum_i[y] += c_i;
col_sum[x] += c;
col_sum_i[x] += c_i;
quad[2 * y / bi.height][2 * x / bi.width] += c;
quad_i[2 * y / bi.height][2 * x / bi.width] += c_i;
total += c;
total_i += c_i;
}
/* Make the sums cummulative */
for (unsigned int i = 1; i < bi.height; i++) {
row_sum[i] += row_sum[i - 1];
row_sum_i[i] += row_sum_i[i - 1];
}
for (unsigned int i = 1; i < bi.width; i++) {
col_sum[i] += col_sum[i - 1];
col_sum_i[i] += col_sum_i[i - 1];
}
const char *best_c = " ";
/* Maybe empty is better! */
if (total < score) {
score = total;
*inverse = false;
best_c = " ";
}
/* Maybe full is better! */
if (total_i < score) {
score = total_i;
*inverse = true;
best_c = " ";
}
/* Find best lower line */
if (1) {
unsigned int best_s = UINT_MAX;
bool best_inv = false;
int best_i = 0;
for (unsigned int i = 0; i < bi.height - 1; i++)
{
unsigned int s;
s = row_sum[i] + total_i - row_sum_i[i];
if (s < best_s) {
best_s = s;
best_i = i;
best_inv = false;
}
s = row_sum_i[i] + total - row_sum[i];
if (s < best_s) {
best_s = s;
best_i = i;
best_inv = true;
}
}
if (best_s < score) {
static const char *lower[7] = {"", "", "", "", "", "", ""};
unsigned int which = lround ((double) ((best_i + 1) * 8) / bi.height);
if (1 <= which && which <= 7) {
score = best_s;
*inverse = best_inv;
best_c = lower[7 - which];
}
}
}
/* Find best left line */
if (1) {
unsigned int best_s = UINT_MAX;
bool best_inv = false;
int best_i = 0;
for (unsigned int i = 0; i < bi.width - 1; i++)
{
unsigned int s;
s = col_sum[i] + total_i - col_sum_i[i];
if (s < best_s) {
best_s = s;
best_i = i;
best_inv = true;
}
s = col_sum_i[i] + total - col_sum[i];
if (s < best_s) {
best_s = s;
best_i = i;
best_inv = false;
}
}
if (best_s < score) {
static const char *left [7] = {"", "", "", "", "", "", ""};
unsigned int which = lround ((double) ((best_i + 1) * 8) / bi.width);
if (1 <= which && which <= 7) {
score = best_s;
*inverse = best_inv;
best_c = left[which - 1];
}
}
}
/* Find best quadrant */
if (1) {
unsigned int q = 0;
unsigned int qs = 0;
for (unsigned int i = 0; i < 2; i++)
for (unsigned int j = 0; j < 2; j++)
if (quad[i][j] > quad_i[i][j]) {
q += 1 << (2 * i + j);
qs += quad_i[i][j];
} else
qs += quad[i][j];
if (qs < score) {
const char *c = nullptr;
bool inv = false;
switch (q) {
case 1: c = ""; inv = true; break;
case 2: c = ""; inv = true; break;
case 4: c = ""; inv = false; break;
case 8: c = ""; inv = false; break;
case 9: c = ""; inv = false; break;
case 6: c = ""; inv = false; break;
case 7: c = ""; inv = true; break;
case 11: c = ""; inv = true; break;
case 13: c = ""; inv = true; break;
case 14: c = ""; inv = true; break;
}
if (c) {
score = qs;
*inverse = inv;
best_c = c;
}
}
}
return best_c;
}
static inline void
ansi_print_image_rgb24 (const uint32_t *data,
unsigned int width,
unsigned int height,
unsigned int stride);
unsigned int stride)
{
image_t image (width, height, data, stride);
unsigned int rows = (height + CELL_H - 1) / CELL_H;
unsigned int cols = (width + CELL_W - 1) / CELL_W;
image_t cell (CELL_W, CELL_H);
biimage_t bi (CELL_W, CELL_H);
unsigned int last_bg = -1, last_fg = -1;
for (unsigned int row = 0; row < rows; row++) {
for (unsigned int col = 0; col < cols; col++) {
image.copy_sub_image (cell, col * CELL_W, row * CELL_H, CELL_W, CELL_H);
bi.set (cell);
if (bi.unicolor) {
if (last_bg != bi.bg) {
printf ("%c[%dm", ESC_E, 40 + bi.bg);
last_bg = bi.bg;
}
printf (" ");
} else {
/* Figure out the closest character to the biimage */
bool inverse = false;
const char *c = block_best (bi, &inverse);
if (inverse) {
if (last_bg != bi.fg || last_fg != bi.bg) {
printf ("%c[%d;%dm", ESC_E, 30 + bi.bg, 40 + bi.fg);
last_bg = bi.fg;
last_fg = bi.bg;
}
} else {
if (last_bg != bi.bg || last_fg != bi.fg) {
printf ("%c[%d;%dm", ESC_E, 40 + bi.bg, 30 + bi.fg);
last_bg = bi.bg;
last_fg = bi.fg;
}
}
printf ("%s", c);
}
}
printf ("%c[0m\n", ESC_E); /* Reset */
last_bg = last_fg = -1;
}
}
#endif

135
util/face-options.hh Normal file
View File

@ -0,0 +1,135 @@
/*
* Copyright © 2011 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Google Author(s): Behdad Esfahbod
*/
#ifndef FACE_OPTIONS_HH
#define FACE_OPTIONS_HH
#include "options.hh"
struct face_options_t
{
void add_options (option_parser_t *parser);
hb_blob_t *get_blob () const;
hb_face_t *get_face () const;
static struct cache_t
{
~cache_t ()
{
free ((void *) font_path);
hb_blob_destroy (blob);
hb_face_destroy (face);
}
const char *font_path = nullptr;
hb_blob_t *blob = nullptr;
unsigned face_index = (unsigned) -1;
hb_face_t *face = nullptr;
} cache;
char *font_file = nullptr;
unsigned face_index = 0;
private:
mutable hb_face_t *face = nullptr;
};
face_options_t::cache_t face_options_t::cache {};
hb_blob_t *
face_options_t::get_blob () const
{
// XXX This does the job for now; will move to post_parse.
return cache.blob;
}
hb_face_t *
face_options_t::get_face () const
{
if (face)
return face;
if (!font_file)
fail (true, "No font file set");
const char *font_path = font_file;
if (0 == strcmp (font_path, "-"))
{
#if defined(_WIN32) || defined(__CYGWIN__)
setmode (fileno (stdin), O_BINARY);
font_path = "STDIN";
#else
font_path = "/dev/stdin";
#endif
}
if (!cache.font_path || 0 != strcmp (cache.font_path, font_path))
{
hb_blob_destroy (cache.blob);
cache.blob = hb_blob_create_from_file_or_fail (font_path);
free ((char *) cache.font_path);
cache.font_path = strdup (font_path);
if (!cache.blob)
fail (false, "%s: Failed reading file", font_path);
hb_face_destroy (cache.face);
cache.face = nullptr;
cache.face_index = (unsigned) -1;
}
if (cache.face_index != face_index)
{
hb_face_destroy (cache.face);
cache.face = hb_face_create (cache.blob, face_index);
cache.face_index = face_index;
}
face = cache.face;
return face;
}
void
face_options_t::add_options (option_parser_t *parser)
{
GOptionEntry entries[] =
{
{"font-file", 0, 0, G_OPTION_ARG_STRING, &this->font_file, "Set font file-name", "filename"},
{"face-index", 0, 0, G_OPTION_ARG_INT, &this->face_index, "Set face index (default: 0)", "index"},
{nullptr}
};
parser->add_group (entries,
"face",
"Font-face options:",
"Options for the font face",
this);
}
#endif

306
util/font-options.hh Normal file
View File

@ -0,0 +1,306 @@
/*
* Copyright © 2011 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Google Author(s): Behdad Esfahbod
*/
#ifndef FONT_OPTIONS_HH
#define FONT_OPTIONS_HH
#include "face-options.hh"
#ifdef HAVE_FREETYPE
#include <hb-ft.h>
#endif
#include <hb-ot.h>
#define FONT_SIZE_UPEM 0x7FFFFFFF
#define FONT_SIZE_NONE 0
extern const unsigned DEFAULT_FONT_SIZE;
extern const unsigned SUBPIXEL_BITS;
struct font_options_t : face_options_t
{
~font_options_t ()
{
g_free (font_file);
free (variations);
g_free (font_funcs);
hb_font_destroy (font);
}
void add_options (option_parser_t *parser);
hb_font_t *get_font () const;
hb_variation_t *variations = nullptr;
unsigned int num_variations = 0;
int x_ppem = 0;
int y_ppem = 0;
double ptem = 0.;
unsigned int subpixel_bits = SUBPIXEL_BITS;
mutable double font_size_x = DEFAULT_FONT_SIZE;
mutable double font_size_y = DEFAULT_FONT_SIZE;
char *font_funcs = nullptr;
int ft_load_flags = 2;
private:
mutable hb_font_t *font = nullptr;
};
static struct supported_font_funcs_t {
char name[4];
void (*func) (hb_font_t *);
} supported_font_funcs[] =
{
#ifdef HAVE_FREETYPE
{"ft", hb_ft_font_set_funcs},
#endif
{"ot", hb_ot_font_set_funcs},
};
hb_font_t *
font_options_t::get_font () const
{
if (font)
return font;
auto *face = get_face ();
font = hb_font_create (face);
if (font_size_x == FONT_SIZE_UPEM)
font_size_x = hb_face_get_upem (face);
if (font_size_y == FONT_SIZE_UPEM)
font_size_y = hb_face_get_upem (face);
hb_font_set_ppem (font, x_ppem, y_ppem);
hb_font_set_ptem (font, ptem);
int scale_x = (int) scalbnf (font_size_x, subpixel_bits);
int scale_y = (int) scalbnf (font_size_y, subpixel_bits);
hb_font_set_scale (font, scale_x, scale_y);
hb_font_set_variations (font, variations, num_variations);
void (*set_font_funcs) (hb_font_t *) = nullptr;
if (!font_funcs)
{
set_font_funcs = supported_font_funcs[0].func;
}
else
{
for (unsigned int i = 0; i < ARRAY_LENGTH (supported_font_funcs); i++)
if (0 == g_ascii_strcasecmp (font_funcs, supported_font_funcs[i].name))
{
set_font_funcs = supported_font_funcs[i].func;
break;
}
if (!set_font_funcs)
{
GString *s = g_string_new (nullptr);
for (unsigned int i = 0; i < ARRAY_LENGTH (supported_font_funcs); i++)
{
if (i)
g_string_append_c (s, '/');
g_string_append (s, supported_font_funcs[i].name);
}
char *p = g_string_free (s, FALSE);
fail (false, "Unknown font function implementation `%s'; supported values are: %s; default is %s",
font_funcs,
p,
supported_font_funcs[0].name);
//free (p);
}
}
set_font_funcs (font);
#ifdef HAVE_FREETYPE
hb_ft_font_set_load_flags (font, ft_load_flags);
#endif
return font;
}
static gboolean
parse_variations (const char *name G_GNUC_UNUSED,
const char *arg,
gpointer data,
GError **error G_GNUC_UNUSED)
{
font_options_t *font_opts = (font_options_t *) data;
char *s = (char *) arg;
char *p;
font_opts->num_variations = 0;
g_free (font_opts->variations);
font_opts->variations = nullptr;
if (!*s)
return true;
/* count the variations first, so we can allocate memory */
p = s;
do {
font_opts->num_variations++;
p = strchr (p, ',');
if (p)
p++;
} while (p);
font_opts->variations = (hb_variation_t *) calloc (font_opts->num_variations, sizeof (*font_opts->variations));
if (!font_opts->variations)
return false;
/* now do the actual parsing */
p = s;
font_opts->num_variations = 0;
while (p && *p) {
char *end = strchr (p, ',');
if (hb_variation_from_string (p, end ? end - p : -1, &font_opts->variations[font_opts->num_variations]))
font_opts->num_variations++;
p = end ? end + 1 : nullptr;
}
return true;
}
static gboolean
parse_font_size (const char *name G_GNUC_UNUSED,
const char *arg,
gpointer data,
GError **error G_GNUC_UNUSED)
{
font_options_t *font_opts = (font_options_t *) data;
if (0 == strcmp (arg, "upem"))
{
font_opts->font_size_y = font_opts->font_size_x = FONT_SIZE_UPEM;
return true;
}
switch (sscanf (arg, "%lf%*[ ,]%lf", &font_opts->font_size_x, &font_opts->font_size_y)) {
case 1: font_opts->font_size_y = font_opts->font_size_x; HB_FALLTHROUGH;
case 2: return true;
default:
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"%s argument should be one or two space-separated numbers",
name);
return false;
}
}
static gboolean
parse_font_ppem (const char *name G_GNUC_UNUSED,
const char *arg,
gpointer data,
GError **error G_GNUC_UNUSED)
{
font_options_t *font_opts = (font_options_t *) data;
switch (sscanf (arg, "%d%*[ ,]%d", &font_opts->x_ppem, &font_opts->y_ppem)) {
case 1: font_opts->y_ppem = font_opts->x_ppem; HB_FALLTHROUGH;
case 2: return true;
default:
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"%s argument should be one or two space-separated numbers",
name);
return false;
}
}
void
font_options_t::add_options (option_parser_t *parser)
{
face_options_t::add_options (parser);
char *text = nullptr;
{
static_assert ((ARRAY_LENGTH_CONST (supported_font_funcs) > 0),
"No supported font-funcs found.");
GString *s = g_string_new (nullptr);
g_string_printf (s, "Set font functions implementation to use (default: %s)\n\n Supported font function implementations are: %s",
supported_font_funcs[0].name,
supported_font_funcs[0].name);
for (unsigned int i = 1; i < ARRAY_LENGTH (supported_font_funcs); i++)
{
g_string_append_c (s, '/');
g_string_append (s, supported_font_funcs[i].name);
}
text = g_string_free (s, FALSE);
parser->free_later (text);
}
char *font_size_text;
if (DEFAULT_FONT_SIZE == FONT_SIZE_UPEM)
font_size_text = (char *) "Font size (default: upem)";
else
{
font_size_text = g_strdup_printf ("Font size (default: %d)", DEFAULT_FONT_SIZE);
parser->free_later (font_size_text);
}
int font_size_flags = DEFAULT_FONT_SIZE == FONT_SIZE_NONE ? G_OPTION_FLAG_HIDDEN : 0;
GOptionEntry entries[] =
{
{"font-size", 0, font_size_flags,
G_OPTION_ARG_CALLBACK, (gpointer) &parse_font_size, font_size_text, "1/2 integers or 'upem'"},
{"font-ppem", 0, font_size_flags,
G_OPTION_ARG_CALLBACK, (gpointer) &parse_font_ppem, "Set x,y pixels per EM (default: 0; disabled)", "1/2 integers"},
{"font-ptem", 0, 0,
G_OPTION_ARG_DOUBLE, &this->ptem, "Set font point-size (default: 0; disabled)", "point-size"},
{"font-funcs", 0, 0, G_OPTION_ARG_STRING, &this->font_funcs, text, "impl"},
{"ft-load-flags", 0, 0, G_OPTION_ARG_INT, &this->ft_load_flags, "Set FreeType load-flags (default: 2)", "integer"},
{nullptr}
};
parser->add_group (entries,
"font",
"Font-instance options:",
"Options for the font instance",
this);
const gchar *variations_help = "Comma-separated list of font variations\n"
"\n"
" Variations are set globally. The format for specifying variation settings\n"
" follows. All valid CSS font-variation-settings values other than 'normal'\n"
" and 'inherited' are also accepted, though, not documented below.\n"
"\n"
" The format is a tag, optionally followed by an equals sign, followed by a\n"
" number. For example:\n"
"\n"
" \"wght=500\"\n"
" \"slnt=-7.5\"\n";
GOptionEntry entries2[] =
{
{"variations", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_variations, variations_help, "list"},
{nullptr}
};
parser->add_group (entries2,
"variations",
"Variations options:",
"Options for font variations used",
this);
}
#endif

View File

@ -24,23 +24,24 @@
* Google Author(s): Behdad Esfahbod
*/
#include "shape-options.hh"
#include "font-options.hh"
#include "text-options.hh"
#include "main-font-text.hh"
const unsigned DEFAULT_FONT_SIZE = FONT_SIZE_NONE;
const unsigned SUBPIXEL_BITS = 0;
#ifdef HAVE_FREETYPE
#include <hb-ft.h>
#endif
struct shape_closure_consumer_t : option_group_t
struct shape_closure_consumer_t
{
shape_closure_consumer_t (option_parser_t *parser) :
shaper (parser),
show_glyph_names (true)
void add_options (struct option_parser_t *parser)
{
add_options (parser);
}
shaper.add_options (parser);
void add_options (struct option_parser_t *parser) override
{
GOptionEntry entries[] =
{
{"no-glyph-names", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &this->show_glyph_names, "Use glyph indices instead of names", nullptr},
@ -53,13 +54,12 @@ struct shape_closure_consumer_t : option_group_t
this);
}
void init (hb_buffer_t *buffer_,
const font_options_t *font_opts)
void init (const font_options_t *font_opts)
{
glyphs = hb_set_create ();
font = hb_font_reference (font_opts->get_font ());
failed = false;
buffer = hb_buffer_reference (buffer_);
buffer = hb_buffer_create ();
}
void consume_line (const char *text,
unsigned int text_len,
@ -104,16 +104,15 @@ struct shape_closure_consumer_t : option_group_t
protected:
shape_options_t shaper;
hb_bool_t show_glyph_names;
hb_bool_t show_glyph_names = false;
hb_set_t *glyphs;
hb_font_t *font;
hb_buffer_t *buffer;
hb_set_t *glyphs = nullptr;
hb_font_t *font = nullptr;
hb_buffer_t *buffer = nullptr;
};
int
main (int argc, char **argv)
{
main_font_text_t<shape_closure_consumer_t, FONT_SIZE_NONE, 0> driver;
return driver.main (argc, argv);
return main_font_text<shape_closure_consumer_t, font_options_t, text_options_t> (argc, argv);
}

View File

@ -25,44 +25,48 @@
* Google Author(s): Behdad Esfahbod
*/
#include "main-font-text.hh"
#include "output-options.hh"
#include "font-options.hh"
#include "text-options.hh"
#include "shape-consumer.hh"
#include "shape-format.hh"
#include "main-font-text.hh"
struct output_buffer_t
const unsigned DEFAULT_FONT_SIZE = FONT_SIZE_UPEM;
const unsigned SUBPIXEL_BITS = 0;
struct output_buffer_t : output_options_t
{
output_buffer_t (option_parser_t *parser)
: options (parser, hb_buffer_serialize_list_formats ()),
format (parser),
gs (nullptr),
line_no (0),
font (nullptr),
output_format (HB_BUFFER_SERIALIZE_FORMAT_INVALID),
format_flags (HB_BUFFER_SERIALIZE_FLAG_DEFAULT) {}
void add_options (option_parser_t *parser)
{
output_options_t::add_options (parser, hb_buffer_serialize_list_formats ());
format.add_options (parser);
}
void init (hb_buffer_t *buffer, const font_options_t *font_opts)
{
options.get_file_handle ();
get_file_handle ();
gs = g_string_new (nullptr);
line_no = 0;
font = hb_font_reference (font_opts->get_font ());
if (!options.output_format)
output_format = HB_BUFFER_SERIALIZE_FORMAT_TEXT;
if (!output_format)
serialize_format = HB_BUFFER_SERIALIZE_FORMAT_TEXT;
else
output_format = hb_buffer_serialize_format_from_string (options.output_format, -1);
serialize_format = hb_buffer_serialize_format_from_string (output_format, -1);
/* An empty "output_format" parameter basically skips output generating.
* Useful for benchmarking. */
if ((!options.output_format || *options.output_format) &&
!hb_buffer_serialize_format_to_string (output_format))
if ((!output_format || *output_format) &&
!hb_buffer_serialize_format_to_string (serialize_format))
{
if (options.explicit_output_format)
if (explicit_output_format)
fail (false, "Unknown output format `%s'; supported formats are: %s",
options.output_format,
g_strjoinv ("/", const_cast<char**> (options.supported_formats)));
output_format,
g_strjoinv ("/", const_cast<char**> (hb_buffer_serialize_list_formats ())));
else
/* Just default to TEXT if not explicitly requested and the
* file extension is not recognized. */
output_format = HB_BUFFER_SERIALIZE_FORMAT_TEXT;
serialize_format = HB_BUFFER_SERIALIZE_FORMAT_TEXT;
}
unsigned int flags = HB_BUFFER_SERIALIZE_FLAG_DEFAULT;
@ -78,7 +82,7 @@ struct output_buffer_t
flags |= HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS;
if (format.show_flags)
flags |= HB_BUFFER_SERIALIZE_FLAG_GLYPH_FLAGS;
format_flags = (hb_buffer_serialize_flags_t) flags;
serialize_flags = (hb_buffer_serialize_flags_t) flags;
if (format.trace)
hb_buffer_set_message_func (buffer, message_func, this, nullptr);
@ -91,13 +95,13 @@ struct output_buffer_t
{
g_string_set_size (gs, 0);
format.serialize_buffer_of_text (buffer, line_no, text, text_len, font, gs);
fprintf (options.fp, "%s", gs->str);
fprintf (fp, "%s", gs->str);
}
void error (const char *message)
{
g_string_set_size (gs, 0);
format.serialize_message (line_no, "error", message, gs);
fprintf (options.fp, "%s", gs->str);
fprintf (fp, "%s", gs->str);
}
void consume_glyphs (hb_buffer_t *buffer,
const char *text,
@ -106,8 +110,8 @@ struct output_buffer_t
{
g_string_set_size (gs, 0);
format.serialize_buffer_of_glyphs (buffer, line_no, text, text_len, font,
output_format, format_flags, gs);
fprintf (options.fp, "%s", gs->str);
serialize_format, serialize_flags, gs);
fprintf (fp, "%s", gs->str);
}
void finish (hb_buffer_t *buffer, const font_options_t *font_opts)
{
@ -137,29 +141,28 @@ struct output_buffer_t
g_string_set_size (gs, 0);
format.serialize_line_no (line_no, gs);
g_string_append_printf (gs, "trace: %s buffer: ", message);
format.serialize (buffer, font, output_format, format_flags, gs);
format.serialize (buffer, font, serialize_format, serialize_flags, gs);
g_string_append_c (gs, '\n');
fprintf (options.fp, "%s", gs->str);
fprintf (fp, "%s", gs->str);
}
protected:
output_options_t options;
format_options_t format;
GString *gs;
unsigned int line_no;
hb_font_t *font;
hb_buffer_serialize_format_t output_format;
hb_buffer_serialize_flags_t format_flags;
shape_format_options_t format;
GString *gs = nullptr;
unsigned int line_no = 0;
hb_font_t *font = nullptr;
hb_buffer_serialize_format_t serialize_format = HB_BUFFER_SERIALIZE_FORMAT_INVALID;
hb_buffer_serialize_flags_t serialize_flags = HB_BUFFER_SERIALIZE_FLAG_DEFAULT;
};
template <int eol = '\n'>
using driver_t = main_font_text_t<shape_consumer_t<output_buffer_t>, FONT_SIZE_UPEM, 0, eol>;
int
main (int argc, char **argv)
{
auto main_func = main_font_text<shape_consumer_t<output_buffer_t>, font_options_t, text_options_t>;
if (argc == 2 && !strcmp (argv[1], "--batch"))
{
unsigned int ret = 0;
@ -184,13 +187,11 @@ main (int argc, char **argv)
start_offset = argc == 2 && p[0] != '\0' && p[0] != ':' && p[1] == ':' && (p[2] == '\\' || p[2] == '/') ? 2 : 0;
}
driver_t<EOF> driver;
ret |= driver.main (argc, args);
ret |= main_func (argc, args, EOF);
fflush (stdout);
}
return ret;
}
driver_t<> driver;
return driver.main (argc, argv);
return main_func (argc, argv, '\n');
}

View File

@ -27,23 +27,29 @@
#include <stdio.h>
#include "subset-options.hh"
#include "output-options.hh"
#include "face-options.hh"
#include "text-options.hh"
#include "main-font-text.hh"
#include "hb-subset.h"
/*
* Command line interface to the harfbuzz font subsetter.
*/
struct subset_consumer_t
{
subset_consumer_t (option_parser_t *parser)
: failed (false), options (parser), subset_options (parser), font (nullptr), input (nullptr) {}
void init (hb_buffer_t *buffer_,
const font_options_t *font_opts)
struct subset_consumer_t : subset_options_t, output_options_t
{
void add_options (option_parser_t *parser)
{
font = hb_font_reference (font_opts->get_font ());
input = hb_subset_input_reference (subset_options.get_input ());
subset_options_t::add_options (parser);
output_options_t::add_options (parser);
}
void init (const face_options_t *face_opts)
{
face = hb_face_reference (face_opts->get_face ());
input = hb_subset_input_reference (get_input ());
}
void consume_line (const char *text,
@ -55,7 +61,6 @@ struct subset_consumer_t
hb_set_t *codepoints = hb_subset_input_unicode_set (input);
if (0 == strcmp (text, "*"))
{
hb_face_t *face = hb_font_get_face (font);
hb_face_collect_unicodes (face, codepoints);
return;
}
@ -94,12 +99,11 @@ struct subset_consumer_t
return true;
}
void finish (const font_options_t *font_opts)
void finish (const face_options_t *face_opts)
{
hb_face_t *face = hb_font_get_face (font);
hb_face_t *new_face = nullptr;
for (unsigned i = 0; i < subset_options.num_iterations; i++) {
for (unsigned i = 0; i < num_iterations; i++)
{
hb_face_destroy (new_face);
new_face = hb_subset_or_fail (face, input);
}
@ -108,31 +112,27 @@ struct subset_consumer_t
if (!failed)
{
hb_blob_t *result = hb_face_reference_blob (new_face);
write_file (options.output_file, result);
write_file (output_file, result);
hb_blob_destroy (result);
}
hb_subset_input_destroy (input);
hb_face_destroy (new_face);
hb_font_destroy (font);
hb_face_destroy (face);
}
public:
bool failed;
bool failed = false;
private:
output_options_t options;
subset_options_t subset_options;
hb_font_t *font;
hb_subset_input_t *input;
hb_face_t *face = nullptr;
hb_subset_input_t *input = nullptr;
};
template <int eol = '\n'>
using driver_t = main_font_text_t<subset_consumer_t, FONT_SIZE_UPEM, 0, eol>;
int
main (int argc, char **argv)
{
auto main_func = main_font_text<subset_consumer_t, face_options_t, text_options_t>;
if (argc == 2 && !strcmp (argv[1], "--batch"))
{
unsigned int ret = 0;
@ -155,8 +155,7 @@ main (int argc, char **argv)
args[argc++] = p = e;
}
driver_t<EOF> driver;
int result = driver.main (argc, args);
int result = main_func (argc, args, EOF);
fprintf (stdout, result == 0 ? "success\n" : "failure\n");
fflush (stdout);
ret |= result;
@ -164,6 +163,5 @@ main (int argc, char **argv)
return ret;
}
driver_t<> driver;
return driver.main (argc, argv);
return main_func (argc, argv, '\n');
}

View File

@ -25,16 +25,17 @@
* Google Author(s): Behdad Esfahbod
*/
#include "main-font-text.hh"
#include "shape-consumer.hh"
#include "view-cairo.hh"
#include "font-options.hh"
#include "text-options.hh"
#include "main-font-text.hh"
#define DEFAULT_FONT_SIZE 256
#define SUBPIXEL_BITS 6
const unsigned DEFAULT_FONT_SIZE = 256;
const unsigned SUBPIXEL_BITS = 6;
int
main (int argc, char **argv)
{
main_font_text_t<shape_consumer_t<view_cairo_t>, DEFAULT_FONT_SIZE, SUBPIXEL_BITS> driver;
return driver.main (argc, argv);
return main_font_text<shape_consumer_t<view_cairo_t>, font_options_t, text_options_t> (argc, argv);
}

View File

@ -1,202 +0,0 @@
/*
* Copyright © 2012 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Google Author(s): Behdad Esfahbod
*/
#include "helper-cairo-ansi.hh"
#include "options.hh"
#include "ansi-print.hh"
#ifdef HAVE_CHAFA
# include <chafa.h>
/* Similar to ansi-print.cc */
# define CELL_W 8
# define CELL_H (2 * CELL_W)
static void
chafa_print_image_rgb24 (const void *data, int width, int height, int stride)
{
ChafaTermInfo *term_info;
ChafaSymbolMap *symbol_map;
ChafaCanvasConfig *config;
ChafaCanvas *canvas;
GString *gs;
unsigned int cols = (width + CELL_W - 1) / CELL_W;
unsigned int rows = (height + CELL_H - 1) / CELL_H;
gchar **environ;
ChafaCanvasMode mode;
ChafaPixelMode pixel_mode;
/* Adapt to terminal; use sixels if available, and fall back to symbols
* with as many colors as are supported */
environ = g_get_environ ();
term_info = chafa_term_db_detect (chafa_term_db_get_default (),
environ);
pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS;
if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_SIXELS))
{
pixel_mode = CHAFA_PIXEL_MODE_SIXELS;
mode = CHAFA_CANVAS_MODE_TRUECOLOR;
}
// else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT))
// mode = CHAFA_CANVAS_MODE_TRUECOLOR;
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_256))
mode = CHAFA_CANVAS_MODE_INDEXED_240;
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_16))
mode = CHAFA_CANVAS_MODE_INDEXED_16;
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_INVERT_COLORS))
mode = CHAFA_CANVAS_MODE_FGBG_BGFG;
else
mode = CHAFA_CANVAS_MODE_FGBG;
/* Create the configuration */
symbol_map = chafa_symbol_map_new ();
chafa_symbol_map_add_by_tags (symbol_map,
(ChafaSymbolTags) (CHAFA_SYMBOL_TAG_BLOCK
| CHAFA_SYMBOL_TAG_SPACE));
config = chafa_canvas_config_new ();
chafa_canvas_config_set_canvas_mode (config, mode);
chafa_canvas_config_set_pixel_mode (config, pixel_mode);
chafa_canvas_config_set_cell_geometry (config, 10, 20);
chafa_canvas_config_set_geometry (config, cols, rows);
chafa_canvas_config_set_symbol_map (config, symbol_map);
chafa_canvas_config_set_color_extractor (config, CHAFA_COLOR_EXTRACTOR_MEDIAN);
chafa_canvas_config_set_work_factor (config, 1.0f);
/* Create canvas, draw to it and render output string */
canvas = chafa_canvas_new (config);
chafa_canvas_draw_all_pixels (canvas,
/* Cairo byte order is host native */
G_BYTE_ORDER == G_LITTLE_ENDIAN
? CHAFA_PIXEL_BGRA8_PREMULTIPLIED
: CHAFA_PIXEL_ARGB8_PREMULTIPLIED,
(const guint8 *) data,
width,
height,
stride);
gs = chafa_canvas_print (canvas, term_info);
/* Print the string */
fwrite (gs->str, sizeof (char), gs->len, stdout);
if (pixel_mode != CHAFA_PIXEL_MODE_SIXELS)
fputc ('\n', stdout);
/* Free resources */
g_string_free (gs, TRUE);
chafa_canvas_unref (canvas);
chafa_canvas_config_unref (config);
chafa_symbol_map_unref (symbol_map);
chafa_term_info_unref (term_info);
g_strfreev (environ);
}
#endif /* HAVE_CHAFA */
cairo_status_t
helper_cairo_surface_write_to_ansi_stream (cairo_surface_t *surface,
cairo_write_func_t write_func,
void *closure)
{
unsigned int width = cairo_image_surface_get_width (surface);
unsigned int height = cairo_image_surface_get_height (surface);
if (cairo_image_surface_get_format (surface) != CAIRO_FORMAT_RGB24) {
cairo_surface_t *new_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
cairo_t *cr = cairo_create (new_surface);
if (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_A8) {
cairo_set_source_rgb (cr, 0., 0., 0.);
cairo_paint (cr);
cairo_set_source_rgb (cr, 1., 1., 1.);
cairo_mask_surface (cr, surface, 0, 0);
} else {
cairo_set_source_rgb (cr, 1., 1., 1.);
cairo_paint (cr);
cairo_set_source_surface (cr, surface, 0, 0);
cairo_paint (cr);
}
cairo_destroy (cr);
surface = new_surface;
} else
cairo_surface_reference (surface);
unsigned int stride = cairo_image_surface_get_stride (surface);
const uint32_t *data = (uint32_t *) (void *) cairo_image_surface_get_data (surface);
/* We don't have rows to spare on the terminal window...
* Find the tight image top/bottom and only print in between. */
/* Use corner color as background color. */
uint32_t bg_color = data ? * (uint32_t *) data : 0;
/* Drop first row while empty */
while (height)
{
unsigned int i;
for (i = 0; i < width; i++)
if (data[i] != bg_color)
break;
if (i < width)
break;
data += stride / 4;
height--;
}
/* Drop last row while empty */
unsigned int orig_height = height;
while (height)
{
const uint32_t *row = data + (height - 1) * stride / 4;
unsigned int i;
for (i = 0; i < width; i++)
if (row[i] != bg_color)
break;
if (i < width)
break;
height--;
}
if (height < orig_height)
height++; /* Add one last blank row for padding. */
if (width && height)
{
#ifdef HAVE_CHAFA
if (true)
chafa_print_image_rgb24 (data, width, height, stride);
else
#endif
ansi_print_image_rgb24 (data, width, height, stride / 4);
}
cairo_surface_destroy (surface);
return CAIRO_STATUS_SUCCESS;
}

View File

@ -31,10 +31,180 @@
#include <cairo.h>
cairo_status_t
#include "ansi-print.hh"
#ifdef HAVE_CHAFA
# include <chafa.h>
/* Similar to ansi-print.cc */
# define CELL_W 8
# define CELL_H (2 * CELL_W)
static void
chafa_print_image_rgb24 (const void *data, int width, int height, int stride)
{
ChafaTermInfo *term_info;
ChafaSymbolMap *symbol_map;
ChafaCanvasConfig *config;
ChafaCanvas *canvas;
GString *gs;
unsigned int cols = (width + CELL_W - 1) / CELL_W;
unsigned int rows = (height + CELL_H - 1) / CELL_H;
gchar **environ;
ChafaCanvasMode mode;
ChafaPixelMode pixel_mode;
/* Adapt to terminal; use sixels if available, and fall back to symbols
* with as many colors as are supported */
environ = g_get_environ ();
term_info = chafa_term_db_detect (chafa_term_db_get_default (),
environ);
pixel_mode = CHAFA_PIXEL_MODE_SYMBOLS;
if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_BEGIN_SIXELS))
{
pixel_mode = CHAFA_PIXEL_MODE_SIXELS;
mode = CHAFA_CANVAS_MODE_TRUECOLOR;
}
// else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_DIRECT))
// mode = CHAFA_CANVAS_MODE_TRUECOLOR;
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_256))
mode = CHAFA_CANVAS_MODE_INDEXED_240;
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_SET_COLOR_FGBG_16))
mode = CHAFA_CANVAS_MODE_INDEXED_16;
else if (chafa_term_info_have_seq (term_info, CHAFA_TERM_SEQ_INVERT_COLORS))
mode = CHAFA_CANVAS_MODE_FGBG_BGFG;
else
mode = CHAFA_CANVAS_MODE_FGBG;
/* Create the configuration */
symbol_map = chafa_symbol_map_new ();
chafa_symbol_map_add_by_tags (symbol_map,
(ChafaSymbolTags) (CHAFA_SYMBOL_TAG_BLOCK
| CHAFA_SYMBOL_TAG_SPACE));
config = chafa_canvas_config_new ();
chafa_canvas_config_set_canvas_mode (config, mode);
chafa_canvas_config_set_pixel_mode (config, pixel_mode);
chafa_canvas_config_set_cell_geometry (config, 10, 20);
chafa_canvas_config_set_geometry (config, cols, rows);
chafa_canvas_config_set_symbol_map (config, symbol_map);
chafa_canvas_config_set_color_extractor (config, CHAFA_COLOR_EXTRACTOR_MEDIAN);
chafa_canvas_config_set_work_factor (config, 1.0f);
/* Create canvas, draw to it and render output string */
canvas = chafa_canvas_new (config);
chafa_canvas_draw_all_pixels (canvas,
/* Cairo byte order is host native */
G_BYTE_ORDER == G_LITTLE_ENDIAN
? CHAFA_PIXEL_BGRA8_PREMULTIPLIED
: CHAFA_PIXEL_ARGB8_PREMULTIPLIED,
(const guint8 *) data,
width,
height,
stride);
gs = chafa_canvas_print (canvas, term_info);
/* Print the string */
fwrite (gs->str, sizeof (char), gs->len, stdout);
if (pixel_mode != CHAFA_PIXEL_MODE_SIXELS)
fputc ('\n', stdout);
/* Free resources */
g_string_free (gs, TRUE);
chafa_canvas_unref (canvas);
chafa_canvas_config_unref (config);
chafa_symbol_map_unref (symbol_map);
chafa_term_info_unref (term_info);
g_strfreev (environ);
}
#endif /* HAVE_CHAFA */
static inline cairo_status_t
helper_cairo_surface_write_to_ansi_stream (cairo_surface_t *surface,
cairo_write_func_t write_func,
void *closure);
void *closure)
{
unsigned int width = cairo_image_surface_get_width (surface);
unsigned int height = cairo_image_surface_get_height (surface);
if (cairo_image_surface_get_format (surface) != CAIRO_FORMAT_RGB24) {
cairo_surface_t *new_surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
cairo_t *cr = cairo_create (new_surface);
if (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_A8) {
cairo_set_source_rgb (cr, 0., 0., 0.);
cairo_paint (cr);
cairo_set_source_rgb (cr, 1., 1., 1.);
cairo_mask_surface (cr, surface, 0, 0);
} else {
cairo_set_source_rgb (cr, 1., 1., 1.);
cairo_paint (cr);
cairo_set_source_surface (cr, surface, 0, 0);
cairo_paint (cr);
}
cairo_destroy (cr);
surface = new_surface;
} else
cairo_surface_reference (surface);
unsigned int stride = cairo_image_surface_get_stride (surface);
const uint32_t *data = (uint32_t *) (void *) cairo_image_surface_get_data (surface);
/* We don't have rows to spare on the terminal window...
* Find the tight image top/bottom and only print in between. */
/* Use corner color as background color. */
uint32_t bg_color = data ? * (uint32_t *) data : 0;
/* Drop first row while empty */
while (height)
{
unsigned int i;
for (i = 0; i < width; i++)
if (data[i] != bg_color)
break;
if (i < width)
break;
data += stride / 4;
height--;
}
/* Drop last row while empty */
unsigned int orig_height = height;
while (height)
{
const uint32_t *row = data + (height - 1) * stride / 4;
unsigned int i;
for (i = 0; i < width; i++)
if (row[i] != bg_color)
break;
if (i < width)
break;
height--;
}
if (height < orig_height)
height++; /* Add one last blank row for padding. */
if (width && height)
{
#ifdef HAVE_CHAFA
if (true)
chafa_print_image_rgb24 (data, width, height, stride);
else
#endif
ansi_print_image_rgb24 (data, width, height, stride / 4);
}
cairo_surface_destroy (surface);
return CAIRO_STATUS_SUCCESS;
}
#endif

View File

@ -1,668 +0,0 @@
/*
* Copyright © 2011 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Google Author(s): Behdad Esfahbod
*/
#include "helper-cairo.hh"
#include <cairo-ft.h>
#include <hb-ft.h>
#include FT_MULTIPLE_MASTERS_H
#include "helper-cairo-ansi.hh"
#ifdef CAIRO_HAS_SVG_SURFACE
# include <cairo-svg.h>
#endif
#ifdef CAIRO_HAS_PDF_SURFACE
# include <cairo-pdf.h>
#endif
#ifdef CAIRO_HAS_PS_SURFACE
# include <cairo-ps.h>
# if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
# define HAS_EPS 1
static cairo_surface_t *
_cairo_eps_surface_create_for_stream (cairo_write_func_t write_func,
void *closure,
double width,
double height)
{
cairo_surface_t *surface;
surface = cairo_ps_surface_create_for_stream (write_func, closure, width, height);
cairo_ps_surface_set_eps (surface, true);
return surface;
}
# else
# undef HAS_EPS
# endif
#endif
static FT_Library ft_library;
#ifdef HAVE_ATEXIT
static inline
void free_ft_library ()
{
FT_Done_FreeType (ft_library);
}
#endif
cairo_scaled_font_t *
helper_cairo_create_scaled_font (const font_options_t *font_opts)
{
hb_font_t *font = hb_font_reference (font_opts->get_font ());
cairo_font_face_t *cairo_face;
/* We cannot use the FT_Face from hb_font_t, as doing so will confuse hb_font_t because
* cairo will reset the face size. As such, create new face...
* TODO Perhaps add API to hb-ft to encapsulate this code. */
FT_Face ft_face = nullptr;//hb_ft_font_get_face (font);
if (!ft_face)
{
if (!ft_library)
{
FT_Init_FreeType (&ft_library);
#ifdef HAVE_ATEXIT
atexit (free_ft_library);
#endif
}
unsigned int blob_length;
const char *blob_data = hb_blob_get_data (font_opts->blob, &blob_length);
if (FT_New_Memory_Face (ft_library,
(const FT_Byte *) blob_data,
blob_length,
font_opts->face_index,
&ft_face))
fail (false, "FT_New_Memory_Face fail");
}
if (!ft_face)
{
/* This allows us to get some boxes at least... */
cairo_face = cairo_toy_font_face_create ("@cairo:sans",
CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_NORMAL);
}
else
{
#ifdef HAVE_FT_SET_VAR_BLEND_COORDINATES
unsigned int num_coords;
const int *coords = hb_font_get_var_coords_normalized (font, &num_coords);
if (num_coords)
{
FT_Fixed *ft_coords = (FT_Fixed *) calloc (num_coords, sizeof (FT_Fixed));
if (ft_coords)
{
for (unsigned int i = 0; i < num_coords; i++)
ft_coords[i] = coords[i] << 2;
FT_Set_Var_Blend_Coordinates (ft_face, num_coords, ft_coords);
free (ft_coords);
}
}
#endif
cairo_face = cairo_ft_font_face_create_for_ft_face (ft_face, font_opts->ft_load_flags);
}
cairo_matrix_t ctm, font_matrix;
cairo_font_options_t *font_options;
cairo_matrix_init_identity (&ctm);
cairo_matrix_init_scale (&font_matrix,
font_opts->font_size_x,
font_opts->font_size_y);
font_options = cairo_font_options_create ();
cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE);
cairo_font_options_set_hint_metrics (font_options, CAIRO_HINT_METRICS_OFF);
cairo_scaled_font_t *scaled_font = cairo_scaled_font_create (cairo_face,
&font_matrix,
&ctm,
font_options);
cairo_font_options_destroy (font_options);
cairo_font_face_destroy (cairo_face);
static cairo_user_data_key_t key;
if (cairo_scaled_font_set_user_data (scaled_font,
&key,
(void *) font,
(cairo_destroy_func_t) hb_font_destroy))
hb_font_destroy (font);
return scaled_font;
}
bool
helper_cairo_scaled_font_has_color (cairo_scaled_font_t *scaled_font)
{
bool ret = false;
#ifdef FT_HAS_COLOR
FT_Face ft_face = cairo_ft_scaled_font_lock_face (scaled_font);
if (ft_face)
{
if (FT_HAS_COLOR (ft_face))
ret = true;
cairo_ft_scaled_font_unlock_face (scaled_font);
}
#endif
return ret;
}
enum class image_protocol_t {
NONE = 0,
ITERM2,
KITTY,
};
struct finalize_closure_t {
void (*callback)(finalize_closure_t *);
cairo_surface_t *surface;
cairo_write_func_t write_func;
void *closure;
image_protocol_t protocol;
};
static cairo_user_data_key_t finalize_closure_key;
static void
finalize_ansi (finalize_closure_t *closure)
{
cairo_status_t status;
status = helper_cairo_surface_write_to_ansi_stream (closure->surface,
closure->write_func,
closure->closure);
if (status != CAIRO_STATUS_SUCCESS)
fail (false, "Failed to write output: %s",
cairo_status_to_string (status));
}
static cairo_surface_t *
_cairo_ansi_surface_create_for_stream (cairo_write_func_t write_func,
void *closure,
double width,
double height,
cairo_content_t content,
image_protocol_t protocol HB_UNUSED)
{
cairo_surface_t *surface;
int w = ceil (width);
int h = ceil (height);
switch (content) {
case CAIRO_CONTENT_ALPHA:
surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
break;
default:
case CAIRO_CONTENT_COLOR:
surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
break;
case CAIRO_CONTENT_COLOR_ALPHA:
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
break;
}
cairo_status_t status = cairo_surface_status (surface);
if (status != CAIRO_STATUS_SUCCESS)
fail (false, "Failed to create cairo surface: %s",
cairo_status_to_string (status));
finalize_closure_t *ansi_closure = g_new0 (finalize_closure_t, 1);
ansi_closure->callback = finalize_ansi;
ansi_closure->surface = surface;
ansi_closure->write_func = write_func;
ansi_closure->closure = closure;
if (cairo_surface_set_user_data (surface,
&finalize_closure_key,
(void *) ansi_closure,
(cairo_destroy_func_t) g_free))
g_free ((void *) closure);
return surface;
}
#ifdef CAIRO_HAS_PNG_FUNCTIONS
static cairo_status_t
byte_array_write_func (void *closure,
const unsigned char *data,
unsigned int size)
{
g_byte_array_append ((GByteArray *) closure, data, size);
return CAIRO_STATUS_SUCCESS;
}
static void
finalize_png (finalize_closure_t *closure)
{
cairo_status_t status;
GByteArray *bytes = nullptr;
GString *string;
gchar *base64;
size_t base64_len;
if (closure->protocol == image_protocol_t::NONE)
{
status = cairo_surface_write_to_png_stream (closure->surface,
closure->write_func,
closure->closure);
}
else
{
bytes = g_byte_array_new ();
status = cairo_surface_write_to_png_stream (closure->surface,
byte_array_write_func,
bytes);
}
if (status != CAIRO_STATUS_SUCCESS)
fail (false, "Failed to write output: %s",
cairo_status_to_string (status));
if (closure->protocol == image_protocol_t::NONE)
return;
base64 = g_base64_encode (bytes->data, bytes->len);
base64_len = strlen (base64);
string = g_string_new (NULL);
if (closure->protocol == image_protocol_t::ITERM2)
{
/* https://iterm2.com/documentation-images.html */
g_string_printf (string, "\033]1337;File=inline=1;size=%zu:%s\a\n",
base64_len, base64);
}
else if (closure->protocol == image_protocol_t::KITTY)
{
#define CHUNK_SIZE 4096
/* https://sw.kovidgoyal.net/kitty/graphics-protocol.html */
for (size_t pos = 0; pos < base64_len; pos += CHUNK_SIZE)
{
size_t len = base64_len - pos;
if (pos == 0)
g_string_append (string, "\033_Ga=T,f=100,m=");
else
g_string_append (string, "\033_Gm=");
if (len > CHUNK_SIZE)
{
g_string_append (string, "1;");
g_string_append_len (string, base64 + pos, CHUNK_SIZE);
}
else
{
g_string_append (string, "0;");
g_string_append_len (string, base64 + pos, len);
}
g_string_append (string, "\033\\");
}
g_string_append (string, "\n");
#undef CHUNK_SIZE
}
closure->write_func (closure->closure, (unsigned char *) string->str, string->len);
g_byte_array_unref (bytes);
g_free (base64);
g_string_free (string, TRUE);
}
static cairo_surface_t *
_cairo_png_surface_create_for_stream (cairo_write_func_t write_func,
void *closure,
double width,
double height,
cairo_content_t content,
image_protocol_t protocol)
{
cairo_surface_t *surface;
int w = ceil (width);
int h = ceil (height);
switch (content) {
case CAIRO_CONTENT_ALPHA:
surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
break;
default:
case CAIRO_CONTENT_COLOR:
surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
break;
case CAIRO_CONTENT_COLOR_ALPHA:
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
break;
}
cairo_status_t status = cairo_surface_status (surface);
if (status != CAIRO_STATUS_SUCCESS)
fail (false, "Failed to create cairo surface: %s",
cairo_status_to_string (status));
finalize_closure_t *png_closure = g_new0 (finalize_closure_t, 1);
png_closure->callback = finalize_png;
png_closure->surface = surface;
png_closure->write_func = write_func;
png_closure->closure = closure;
png_closure->protocol = protocol;
if (cairo_surface_set_user_data (surface,
&finalize_closure_key,
(void *) png_closure,
(cairo_destroy_func_t) g_free))
g_free ((void *) closure);
return surface;
}
#endif
static cairo_status_t
stdio_write_func (void *closure,
const unsigned char *data,
unsigned int size)
{
FILE *fp = (FILE *) closure;
while (size) {
size_t ret = fwrite (data, 1, size, fp);
size -= ret;
data += ret;
if (size && ferror (fp))
fail (false, "Failed to write output: %s", strerror (errno));
}
return CAIRO_STATUS_SUCCESS;
}
const char *helper_cairo_supported_formats[] =
{
"ansi",
#ifdef CAIRO_HAS_PNG_FUNCTIONS
"png",
#endif
#ifdef CAIRO_HAS_SVG_SURFACE
"svg",
#endif
#ifdef CAIRO_HAS_PDF_SURFACE
"pdf",
#endif
#ifdef CAIRO_HAS_PS_SURFACE
"ps",
#ifdef HAS_EPS
"eps",
#endif
#endif
nullptr
};
cairo_t *
helper_cairo_create_context (double w, double h,
view_options_t *view_opts,
output_options_t *out_opts,
cairo_content_t content)
{
cairo_surface_t *(*constructor) (cairo_write_func_t write_func,
void *closure,
double width,
double height) = nullptr;
cairo_surface_t *(*constructor2) (cairo_write_func_t write_func,
void *closure,
double width,
double height,
cairo_content_t content,
image_protocol_t protocol) = nullptr;
image_protocol_t protocol = image_protocol_t::NONE;
const char *extension = out_opts->output_format;
if (!extension) {
#if HAVE_ISATTY
if (isatty (fileno (out_opts->get_file_handle ())))
{
#ifdef CAIRO_HAS_PNG_FUNCTIONS
const char *name;
/* https://gitlab.com/gnachman/iterm2/-/issues/7154 */
if ((name = getenv ("LC_TERMINAL")) != nullptr &&
0 == g_ascii_strcasecmp (name, "iTerm2"))
{
extension = "png";
protocol = image_protocol_t::ITERM2;
}
else if ((name = getenv ("TERM")) != nullptr &&
0 == g_ascii_strcasecmp (name, "xterm-kitty"))
{
extension = "png";
protocol = image_protocol_t::KITTY;
}
else
extension = "ansi";
#else
extension = "ansi";
#endif
}
else
#endif
{
#ifdef CAIRO_HAS_PNG_FUNCTIONS
extension = "png";
#else
extension = "ansi";
#endif
}
}
if (0)
;
else if (0 == g_ascii_strcasecmp (extension, "ansi"))
constructor2 = _cairo_ansi_surface_create_for_stream;
#ifdef CAIRO_HAS_PNG_FUNCTIONS
else if (0 == g_ascii_strcasecmp (extension, "png"))
constructor2 = _cairo_png_surface_create_for_stream;
#endif
#ifdef CAIRO_HAS_SVG_SURFACE
else if (0 == g_ascii_strcasecmp (extension, "svg"))
constructor = cairo_svg_surface_create_for_stream;
#endif
#ifdef CAIRO_HAS_PDF_SURFACE
else if (0 == g_ascii_strcasecmp (extension, "pdf"))
constructor = cairo_pdf_surface_create_for_stream;
#endif
#ifdef CAIRO_HAS_PS_SURFACE
else if (0 == g_ascii_strcasecmp (extension, "ps"))
constructor = cairo_ps_surface_create_for_stream;
#ifdef HAS_EPS
else if (0 == g_ascii_strcasecmp (extension, "eps"))
constructor = _cairo_eps_surface_create_for_stream;
#endif
#endif
unsigned int fr, fg, fb, fa, br, bg, bb, ba;
const char *color;
br = bg = bb = 0; ba = 255;
color = view_opts->back ? view_opts->back : DEFAULT_BACK;
sscanf (color + (*color=='#'), "%2x%2x%2x%2x", &br, &bg, &bb, &ba);
fr = fg = fb = 0; fa = 255;
color = view_opts->fore ? view_opts->fore : DEFAULT_FORE;
sscanf (color + (*color=='#'), "%2x%2x%2x%2x", &fr, &fg, &fb, &fa);
if (content == CAIRO_CONTENT_ALPHA)
{
if (view_opts->annotate ||
br != bg || bg != bb ||
fr != fg || fg != fb)
content = CAIRO_CONTENT_COLOR;
}
if (ba != 255)
content = CAIRO_CONTENT_COLOR_ALPHA;
cairo_surface_t *surface;
FILE *f = out_opts->get_file_handle ();
if (constructor)
surface = constructor (stdio_write_func, f, w, h);
else if (constructor2)
surface = constructor2 (stdio_write_func, f, w, h, content, protocol);
else
fail (false, "Unknown output format `%s'; supported formats are: %s%s",
extension,
g_strjoinv ("/", const_cast<char**> (helper_cairo_supported_formats)),
out_opts->explicit_output_format ? "" :
"\nTry setting format using --output-format");
cairo_t *cr = cairo_create (surface);
content = cairo_surface_get_content (surface);
switch (content) {
case CAIRO_CONTENT_ALPHA:
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_rgba (cr, 1., 1., 1., br / 255.);
cairo_paint (cr);
cairo_set_source_rgba (cr, 1., 1., 1.,
(fr / 255.) * (fa / 255.) + (br / 255) * (1 - (fa / 255.)));
break;
default:
case CAIRO_CONTENT_COLOR:
case CAIRO_CONTENT_COLOR_ALPHA:
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_rgba (cr, br / 255., bg / 255., bb / 255., ba / 255.);
cairo_paint (cr);
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
cairo_set_source_rgba (cr, fr / 255., fg / 255., fb / 255., fa / 255.);
break;
}
cairo_surface_destroy (surface);
return cr;
}
void
helper_cairo_destroy_context (cairo_t *cr)
{
finalize_closure_t *closure = (finalize_closure_t *)
cairo_surface_get_user_data (cairo_get_target (cr),
&finalize_closure_key);
if (closure)
closure->callback (closure);
cairo_status_t status = cairo_status (cr);
if (status != CAIRO_STATUS_SUCCESS)
fail (false, "Failed: %s",
cairo_status_to_string (status));
cairo_destroy (cr);
}
void
helper_cairo_line_from_buffer (helper_cairo_line_t *l,
hb_buffer_t *buffer,
const char *text,
unsigned int text_len,
int scale_bits,
hb_bool_t utf8_clusters)
{
memset (l, 0, sizeof (*l));
l->num_glyphs = hb_buffer_get_length (buffer);
hb_glyph_info_t *hb_glyph = hb_buffer_get_glyph_infos (buffer, nullptr);
hb_glyph_position_t *hb_position = hb_buffer_get_glyph_positions (buffer, nullptr);
l->glyphs = cairo_glyph_allocate (l->num_glyphs + 1);
if (text) {
l->utf8 = g_strndup (text, text_len);
l->utf8_len = text_len;
l->num_clusters = l->num_glyphs ? 1 : 0;
for (unsigned int i = 1; i < l->num_glyphs; i++)
if (hb_glyph[i].cluster != hb_glyph[i-1].cluster)
l->num_clusters++;
l->clusters = cairo_text_cluster_allocate (l->num_clusters);
}
if ((l->num_glyphs && !l->glyphs) ||
(l->utf8_len && !l->utf8) ||
(l->num_clusters && !l->clusters))
{
l->finish ();
return;
}
hb_position_t x = 0, y = 0;
int i;
for (i = 0; i < (int) l->num_glyphs; i++)
{
l->glyphs[i].index = hb_glyph[i].codepoint;
l->glyphs[i].x = scalbn ((double) hb_position->x_offset + x, scale_bits);
l->glyphs[i].y = scalbn ((double) -hb_position->y_offset + y, scale_bits);
x += hb_position->x_advance;
y += -hb_position->y_advance;
hb_position++;
}
l->glyphs[i].index = -1;
l->glyphs[i].x = scalbn ((double) x, scale_bits);
l->glyphs[i].y = scalbn ((double) y, scale_bits);
if (l->num_clusters) {
memset ((void *) l->clusters, 0, l->num_clusters * sizeof (l->clusters[0]));
hb_bool_t backward = HB_DIRECTION_IS_BACKWARD (hb_buffer_get_direction (buffer));
l->cluster_flags = backward ? CAIRO_TEXT_CLUSTER_FLAG_BACKWARD : (cairo_text_cluster_flags_t) 0;
unsigned int cluster = 0;
const char *start = l->utf8, *end;
l->clusters[cluster].num_glyphs++;
if (backward) {
for (i = l->num_glyphs - 2; i >= 0; i--) {
if (hb_glyph[i].cluster != hb_glyph[i+1].cluster) {
g_assert (hb_glyph[i].cluster > hb_glyph[i+1].cluster);
if (utf8_clusters)
end = start + hb_glyph[i].cluster - hb_glyph[i+1].cluster;
else
end = g_utf8_offset_to_pointer (start, hb_glyph[i].cluster - hb_glyph[i+1].cluster);
l->clusters[cluster].num_bytes = end - start;
start = end;
cluster++;
}
l->clusters[cluster].num_glyphs++;
}
l->clusters[cluster].num_bytes = l->utf8 + text_len - start;
} else {
for (i = 1; i < (int) l->num_glyphs; i++) {
if (hb_glyph[i].cluster != hb_glyph[i-1].cluster) {
g_assert (hb_glyph[i].cluster > hb_glyph[i-1].cluster);
if (utf8_clusters)
end = start + hb_glyph[i].cluster - hb_glyph[i-1].cluster;
else
end = g_utf8_offset_to_pointer (start, hb_glyph[i].cluster - hb_glyph[i-1].cluster);
l->clusters[cluster].num_bytes = end - start;
start = end;
cluster++;
}
l->clusters[cluster].num_glyphs++;
}
l->clusters[cluster].num_bytes = l->utf8 + text_len - start;
}
}
}

View File

@ -27,28 +27,558 @@
#ifndef HELPER_CAIRO_HH
#define HELPER_CAIRO_HH
#include "hb.hh"
#include "options.hh"
#include "view-options.hh"
#include "output-options.hh"
#include <cairo.h>
#include <cairo-ft.h>
#include <hb-ft.h>
#include FT_MULTIPLE_MASTERS_H
#include "helper-cairo-ansi.hh"
#ifdef CAIRO_HAS_SVG_SURFACE
# include <cairo-svg.h>
#endif
#ifdef CAIRO_HAS_PDF_SURFACE
# include <cairo-pdf.h>
#endif
#ifdef CAIRO_HAS_PS_SURFACE
# include <cairo-ps.h>
# if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1,6,0)
# define HAS_EPS 1
static cairo_surface_t *
_cairo_eps_surface_create_for_stream (cairo_write_func_t write_func,
void *closure,
double width,
double height)
{
cairo_surface_t *surface;
surface = cairo_ps_surface_create_for_stream (write_func, closure, width, height);
cairo_ps_surface_set_eps (surface, true);
return surface;
}
# else
# undef HAS_EPS
# endif
#endif
cairo_scaled_font_t *
helper_cairo_create_scaled_font (const font_options_t *font_opts);
static FT_Library ft_library;
bool
helper_cairo_scaled_font_has_color (cairo_scaled_font_t *scaled_font);
#ifdef HAVE_ATEXIT
static inline
void free_ft_library ()
{
FT_Done_FreeType (ft_library);
}
#endif
extern const char *helper_cairo_supported_formats[];
static inline cairo_scaled_font_t *
helper_cairo_create_scaled_font (const font_options_t *font_opts)
{
hb_font_t *font = hb_font_reference (font_opts->get_font ());
cairo_t *
cairo_font_face_t *cairo_face;
/* We cannot use the FT_Face from hb_font_t, as doing so will confuse hb_font_t because
* cairo will reset the face size. As such, create new face...
* TODO Perhaps add API to hb-ft to encapsulate this code. */
FT_Face ft_face = nullptr;//hb_ft_font_get_face (font);
if (!ft_face)
{
if (!ft_library)
{
FT_Init_FreeType (&ft_library);
#ifdef HAVE_ATEXIT
atexit (free_ft_library);
#endif
}
unsigned int blob_length;
const char *blob_data = hb_blob_get_data (font_opts->get_blob (), &blob_length);
if (FT_New_Memory_Face (ft_library,
(const FT_Byte *) blob_data,
blob_length,
font_opts->face_index,
&ft_face))
fail (false, "FT_New_Memory_Face fail");
}
if (!ft_face)
{
/* This allows us to get some boxes at least... */
cairo_face = cairo_toy_font_face_create ("@cairo:sans",
CAIRO_FONT_SLANT_NORMAL,
CAIRO_FONT_WEIGHT_NORMAL);
}
else
{
#ifdef HAVE_FT_SET_VAR_BLEND_COORDINATES
unsigned int num_coords;
const int *coords = hb_font_get_var_coords_normalized (font, &num_coords);
if (num_coords)
{
FT_Fixed *ft_coords = (FT_Fixed *) calloc (num_coords, sizeof (FT_Fixed));
if (ft_coords)
{
for (unsigned int i = 0; i < num_coords; i++)
ft_coords[i] = coords[i] << 2;
FT_Set_Var_Blend_Coordinates (ft_face, num_coords, ft_coords);
free (ft_coords);
}
}
#endif
cairo_face = cairo_ft_font_face_create_for_ft_face (ft_face, font_opts->ft_load_flags);
}
cairo_matrix_t ctm, font_matrix;
cairo_font_options_t *font_options;
cairo_matrix_init_identity (&ctm);
cairo_matrix_init_scale (&font_matrix,
font_opts->font_size_x,
font_opts->font_size_y);
font_options = cairo_font_options_create ();
cairo_font_options_set_hint_style (font_options, CAIRO_HINT_STYLE_NONE);
cairo_font_options_set_hint_metrics (font_options, CAIRO_HINT_METRICS_OFF);
cairo_scaled_font_t *scaled_font = cairo_scaled_font_create (cairo_face,
&font_matrix,
&ctm,
font_options);
cairo_font_options_destroy (font_options);
cairo_font_face_destroy (cairo_face);
static cairo_user_data_key_t key;
if (cairo_scaled_font_set_user_data (scaled_font,
&key,
(void *) font,
(cairo_destroy_func_t) hb_font_destroy))
hb_font_destroy (font);
return scaled_font;
}
static inline bool
helper_cairo_scaled_font_has_color (cairo_scaled_font_t *scaled_font)
{
bool ret = false;
#ifdef FT_HAS_COLOR
FT_Face ft_face = cairo_ft_scaled_font_lock_face (scaled_font);
if (ft_face)
{
if (FT_HAS_COLOR (ft_face))
ret = true;
cairo_ft_scaled_font_unlock_face (scaled_font);
}
#endif
return ret;
}
enum class image_protocol_t {
NONE = 0,
ITERM2,
KITTY,
};
struct finalize_closure_t {
void (*callback)(finalize_closure_t *);
cairo_surface_t *surface;
cairo_write_func_t write_func;
void *closure;
image_protocol_t protocol;
};
static cairo_user_data_key_t finalize_closure_key;
static void
finalize_ansi (finalize_closure_t *closure)
{
cairo_status_t status;
status = helper_cairo_surface_write_to_ansi_stream (closure->surface,
closure->write_func,
closure->closure);
if (status != CAIRO_STATUS_SUCCESS)
fail (false, "Failed to write output: %s",
cairo_status_to_string (status));
}
static cairo_surface_t *
_cairo_ansi_surface_create_for_stream (cairo_write_func_t write_func,
void *closure,
double width,
double height,
cairo_content_t content,
image_protocol_t protocol HB_UNUSED)
{
cairo_surface_t *surface;
int w = ceil (width);
int h = ceil (height);
switch (content) {
case CAIRO_CONTENT_ALPHA:
surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
break;
default:
case CAIRO_CONTENT_COLOR:
surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
break;
case CAIRO_CONTENT_COLOR_ALPHA:
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
break;
}
cairo_status_t status = cairo_surface_status (surface);
if (status != CAIRO_STATUS_SUCCESS)
fail (false, "Failed to create cairo surface: %s",
cairo_status_to_string (status));
finalize_closure_t *ansi_closure = g_new0 (finalize_closure_t, 1);
ansi_closure->callback = finalize_ansi;
ansi_closure->surface = surface;
ansi_closure->write_func = write_func;
ansi_closure->closure = closure;
if (cairo_surface_set_user_data (surface,
&finalize_closure_key,
(void *) ansi_closure,
(cairo_destroy_func_t) g_free))
g_free ((void *) closure);
return surface;
}
#ifdef CAIRO_HAS_PNG_FUNCTIONS
static cairo_status_t
byte_array_write_func (void *closure,
const unsigned char *data,
unsigned int size)
{
g_byte_array_append ((GByteArray *) closure, data, size);
return CAIRO_STATUS_SUCCESS;
}
static void
finalize_png (finalize_closure_t *closure)
{
cairo_status_t status;
GByteArray *bytes = nullptr;
GString *string;
gchar *base64;
size_t base64_len;
if (closure->protocol == image_protocol_t::NONE)
{
status = cairo_surface_write_to_png_stream (closure->surface,
closure->write_func,
closure->closure);
}
else
{
bytes = g_byte_array_new ();
status = cairo_surface_write_to_png_stream (closure->surface,
byte_array_write_func,
bytes);
}
if (status != CAIRO_STATUS_SUCCESS)
fail (false, "Failed to write output: %s",
cairo_status_to_string (status));
if (closure->protocol == image_protocol_t::NONE)
return;
base64 = g_base64_encode (bytes->data, bytes->len);
base64_len = strlen (base64);
string = g_string_new (NULL);
if (closure->protocol == image_protocol_t::ITERM2)
{
/* https://iterm2.com/documentation-images.html */
g_string_printf (string, "\033]1337;File=inline=1;size=%zu:%s\a\n",
base64_len, base64);
}
else if (closure->protocol == image_protocol_t::KITTY)
{
#define CHUNK_SIZE 4096
/* https://sw.kovidgoyal.net/kitty/graphics-protocol.html */
for (size_t pos = 0; pos < base64_len; pos += CHUNK_SIZE)
{
size_t len = base64_len - pos;
if (pos == 0)
g_string_append (string, "\033_Ga=T,f=100,m=");
else
g_string_append (string, "\033_Gm=");
if (len > CHUNK_SIZE)
{
g_string_append (string, "1;");
g_string_append_len (string, base64 + pos, CHUNK_SIZE);
}
else
{
g_string_append (string, "0;");
g_string_append_len (string, base64 + pos, len);
}
g_string_append (string, "\033\\");
}
g_string_append (string, "\n");
#undef CHUNK_SIZE
}
closure->write_func (closure->closure, (unsigned char *) string->str, string->len);
g_byte_array_unref (bytes);
g_free (base64);
g_string_free (string, TRUE);
}
static cairo_surface_t *
_cairo_png_surface_create_for_stream (cairo_write_func_t write_func,
void *closure,
double width,
double height,
cairo_content_t content,
image_protocol_t protocol)
{
cairo_surface_t *surface;
int w = ceil (width);
int h = ceil (height);
switch (content) {
case CAIRO_CONTENT_ALPHA:
surface = cairo_image_surface_create (CAIRO_FORMAT_A8, w, h);
break;
default:
case CAIRO_CONTENT_COLOR:
surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, w, h);
break;
case CAIRO_CONTENT_COLOR_ALPHA:
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, w, h);
break;
}
cairo_status_t status = cairo_surface_status (surface);
if (status != CAIRO_STATUS_SUCCESS)
fail (false, "Failed to create cairo surface: %s",
cairo_status_to_string (status));
finalize_closure_t *png_closure = g_new0 (finalize_closure_t, 1);
png_closure->callback = finalize_png;
png_closure->surface = surface;
png_closure->write_func = write_func;
png_closure->closure = closure;
png_closure->protocol = protocol;
if (cairo_surface_set_user_data (surface,
&finalize_closure_key,
(void *) png_closure,
(cairo_destroy_func_t) g_free))
g_free ((void *) closure);
return surface;
}
#endif
static cairo_status_t
stdio_write_func (void *closure,
const unsigned char *data,
unsigned int size)
{
FILE *fp = (FILE *) closure;
while (size) {
size_t ret = fwrite (data, 1, size, fp);
size -= ret;
data += ret;
if (size && ferror (fp))
fail (false, "Failed to write output: %s", strerror (errno));
}
return CAIRO_STATUS_SUCCESS;
}
static const char *helper_cairo_supported_formats[] =
{
"ansi",
#ifdef CAIRO_HAS_PNG_FUNCTIONS
"png",
#endif
#ifdef CAIRO_HAS_SVG_SURFACE
"svg",
#endif
#ifdef CAIRO_HAS_PDF_SURFACE
"pdf",
#endif
#ifdef CAIRO_HAS_PS_SURFACE
"ps",
#ifdef HAS_EPS
"eps",
#endif
#endif
nullptr
};
static inline cairo_t *
helper_cairo_create_context (double w, double h,
view_options_t *view_opts,
output_options_t *out_opts,
cairo_content_t content);
cairo_content_t content)
{
cairo_surface_t *(*constructor) (cairo_write_func_t write_func,
void *closure,
double width,
double height) = nullptr;
cairo_surface_t *(*constructor2) (cairo_write_func_t write_func,
void *closure,
double width,
double height,
cairo_content_t content,
image_protocol_t protocol) = nullptr;
void
helper_cairo_destroy_context (cairo_t *cr);
image_protocol_t protocol = image_protocol_t::NONE;
const char *extension = out_opts->output_format;
if (!extension) {
#if HAVE_ISATTY
if (isatty (fileno (out_opts->get_file_handle ())))
{
#ifdef CAIRO_HAS_PNG_FUNCTIONS
const char *name;
/* https://gitlab.com/gnachman/iterm2/-/issues/7154 */
if ((name = getenv ("LC_TERMINAL")) != nullptr &&
0 == g_ascii_strcasecmp (name, "iTerm2"))
{
extension = "png";
protocol = image_protocol_t::ITERM2;
}
else if ((name = getenv ("TERM")) != nullptr &&
0 == g_ascii_strcasecmp (name, "xterm-kitty"))
{
extension = "png";
protocol = image_protocol_t::KITTY;
}
else
extension = "ansi";
#else
extension = "ansi";
#endif
}
else
#endif
{
#ifdef CAIRO_HAS_PNG_FUNCTIONS
extension = "png";
#else
extension = "ansi";
#endif
}
}
if (0)
;
else if (0 == g_ascii_strcasecmp (extension, "ansi"))
constructor2 = _cairo_ansi_surface_create_for_stream;
#ifdef CAIRO_HAS_PNG_FUNCTIONS
else if (0 == g_ascii_strcasecmp (extension, "png"))
constructor2 = _cairo_png_surface_create_for_stream;
#endif
#ifdef CAIRO_HAS_SVG_SURFACE
else if (0 == g_ascii_strcasecmp (extension, "svg"))
constructor = cairo_svg_surface_create_for_stream;
#endif
#ifdef CAIRO_HAS_PDF_SURFACE
else if (0 == g_ascii_strcasecmp (extension, "pdf"))
constructor = cairo_pdf_surface_create_for_stream;
#endif
#ifdef CAIRO_HAS_PS_SURFACE
else if (0 == g_ascii_strcasecmp (extension, "ps"))
constructor = cairo_ps_surface_create_for_stream;
#ifdef HAS_EPS
else if (0 == g_ascii_strcasecmp (extension, "eps"))
constructor = _cairo_eps_surface_create_for_stream;
#endif
#endif
unsigned int fr, fg, fb, fa, br, bg, bb, ba;
const char *color;
br = bg = bb = 0; ba = 255;
color = view_opts->back ? view_opts->back : DEFAULT_BACK;
sscanf (color + (*color=='#'), "%2x%2x%2x%2x", &br, &bg, &bb, &ba);
fr = fg = fb = 0; fa = 255;
color = view_opts->fore ? view_opts->fore : DEFAULT_FORE;
sscanf (color + (*color=='#'), "%2x%2x%2x%2x", &fr, &fg, &fb, &fa);
if (content == CAIRO_CONTENT_ALPHA)
{
if (view_opts->annotate ||
br != bg || bg != bb ||
fr != fg || fg != fb)
content = CAIRO_CONTENT_COLOR;
}
if (ba != 255)
content = CAIRO_CONTENT_COLOR_ALPHA;
cairo_surface_t *surface;
FILE *f = out_opts->get_file_handle ();
if (constructor)
surface = constructor (stdio_write_func, f, w, h);
else if (constructor2)
surface = constructor2 (stdio_write_func, f, w, h, content, protocol);
else
fail (false, "Unknown output format `%s'; supported formats are: %s%s",
extension,
g_strjoinv ("/", const_cast<char**> (helper_cairo_supported_formats)),
out_opts->explicit_output_format ? "" :
"\nTry setting format using --output-format");
cairo_t *cr = cairo_create (surface);
content = cairo_surface_get_content (surface);
switch (content) {
case CAIRO_CONTENT_ALPHA:
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_rgba (cr, 1., 1., 1., br / 255.);
cairo_paint (cr);
cairo_set_source_rgba (cr, 1., 1., 1.,
(fr / 255.) * (fa / 255.) + (br / 255) * (1 - (fa / 255.)));
break;
default:
case CAIRO_CONTENT_COLOR:
case CAIRO_CONTENT_COLOR_ALPHA:
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_rgba (cr, br / 255., bg / 255., bb / 255., ba / 255.);
cairo_paint (cr);
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
cairo_set_source_rgba (cr, fr / 255., fg / 255., fb / 255., fa / 255.);
break;
}
cairo_surface_destroy (surface);
return cr;
}
static inline void
helper_cairo_destroy_context (cairo_t *cr)
{
finalize_closure_t *closure = (finalize_closure_t *)
cairo_surface_get_user_data (cairo_get_target (cr),
&finalize_closure_key);
if (closure)
closure->callback (closure);
cairo_status_t status = cairo_status (cr);
if (status != CAIRO_STATUS_SUCCESS)
fail (false, "Failed: %s",
cairo_status_to_string (status));
cairo_destroy (cr);
}
struct helper_cairo_line_t {
@ -75,12 +605,94 @@ struct helper_cairo_line_t {
}
};
void
static inline void
helper_cairo_line_from_buffer (helper_cairo_line_t *l,
hb_buffer_t *buffer,
const char *text,
unsigned int text_len,
int scale_bits,
hb_bool_t utf8_clusters);
hb_bool_t utf8_clusters)
{
memset (l, 0, sizeof (*l));
l->num_glyphs = hb_buffer_get_length (buffer);
hb_glyph_info_t *hb_glyph = hb_buffer_get_glyph_infos (buffer, nullptr);
hb_glyph_position_t *hb_position = hb_buffer_get_glyph_positions (buffer, nullptr);
l->glyphs = cairo_glyph_allocate (l->num_glyphs + 1);
if (text) {
l->utf8 = g_strndup (text, text_len);
l->utf8_len = text_len;
l->num_clusters = l->num_glyphs ? 1 : 0;
for (unsigned int i = 1; i < l->num_glyphs; i++)
if (hb_glyph[i].cluster != hb_glyph[i-1].cluster)
l->num_clusters++;
l->clusters = cairo_text_cluster_allocate (l->num_clusters);
}
if ((l->num_glyphs && !l->glyphs) ||
(l->utf8_len && !l->utf8) ||
(l->num_clusters && !l->clusters))
{
l->finish ();
return;
}
hb_position_t x = 0, y = 0;
int i;
for (i = 0; i < (int) l->num_glyphs; i++)
{
l->glyphs[i].index = hb_glyph[i].codepoint;
l->glyphs[i].x = scalbn ((double) hb_position->x_offset + x, scale_bits);
l->glyphs[i].y = scalbn ((double) -hb_position->y_offset + y, scale_bits);
x += hb_position->x_advance;
y += -hb_position->y_advance;
hb_position++;
}
l->glyphs[i].index = -1;
l->glyphs[i].x = scalbn ((double) x, scale_bits);
l->glyphs[i].y = scalbn ((double) y, scale_bits);
if (l->num_clusters) {
memset ((void *) l->clusters, 0, l->num_clusters * sizeof (l->clusters[0]));
hb_bool_t backward = HB_DIRECTION_IS_BACKWARD (hb_buffer_get_direction (buffer));
l->cluster_flags = backward ? CAIRO_TEXT_CLUSTER_FLAG_BACKWARD : (cairo_text_cluster_flags_t) 0;
unsigned int cluster = 0;
const char *start = l->utf8, *end;
l->clusters[cluster].num_glyphs++;
if (backward) {
for (i = l->num_glyphs - 2; i >= 0; i--) {
if (hb_glyph[i].cluster != hb_glyph[i+1].cluster) {
g_assert (hb_glyph[i].cluster > hb_glyph[i+1].cluster);
if (utf8_clusters)
end = start + hb_glyph[i].cluster - hb_glyph[i+1].cluster;
else
end = g_utf8_offset_to_pointer (start, hb_glyph[i].cluster - hb_glyph[i+1].cluster);
l->clusters[cluster].num_bytes = end - start;
start = end;
cluster++;
}
l->clusters[cluster].num_glyphs++;
}
l->clusters[cluster].num_bytes = l->utf8 + text_len - start;
} else {
for (i = 1; i < (int) l->num_glyphs; i++) {
if (hb_glyph[i].cluster != hb_glyph[i-1].cluster) {
g_assert (hb_glyph[i].cluster > hb_glyph[i-1].cluster);
if (utf8_clusters)
end = start + hb_glyph[i].cluster - hb_glyph[i-1].cluster;
else
end = g_utf8_offset_to_pointer (start, hb_glyph[i].cluster - hb_glyph[i-1].cluster);
l->clusters[cluster].num_bytes = end - start;
start = end;
cluster++;
}
l->clusters[cluster].num_glyphs++;
}
l->clusters[cluster].num_bytes = l->utf8 + text_len - start;
}
}
}
#endif

View File

@ -27,70 +27,45 @@
#ifndef HB_MAIN_FONT_TEXT_HH
#define HB_MAIN_FONT_TEXT_HH
#include "hb.hh"
#include "options.hh"
/* main() body for utilities taking font and processing text.*/
static char *
locale_to_utf8 (char *s)
template <typename consumer_t, typename font_options_t, typename text_options_t>
int
main_font_text (int argc, char **argv, int eol = '\n')
{
char *t;
GError *error = nullptr;
t = g_locale_to_utf8 (s, -1, nullptr, nullptr, &error);
if (!t)
{
fail (true, "Failed converting text to UTF-8");
}
return t;
}
template <typename consumer_t, int default_font_size, int subpixel_bits, int eol = '\n'>
struct main_font_text_t
{
main_font_text_t ()
: options ("[FONT-FILE] [TEXT]"),
font_opts (&options, default_font_size, subpixel_bits),
input (&options),
consumer (&options) {}
int
main (int argc, char **argv)
{
options.parse (&argc, &argv);
argc--, argv++;
if (argc && !font_opts.font_file) font_opts.font_file = locale_to_utf8 (argv[0]), argc--, argv++;
if (argc && !input.text && !input.text_file) input.text = locale_to_utf8 (argv[0]), argc--, argv++;
if (argc)
fail (true, "Too many arguments on the command line");
if (!font_opts.font_file)
options.usage ();
if (!input.text && !input.text_file)
input.text_file = g_strdup ("-");
hb_buffer_t *buffer = hb_buffer_create ();
consumer.init (buffer, &font_opts);
hb_buffer_destroy (buffer);
unsigned int text_len;
const char *text;
while ((text = input.get_line (&text_len, eol)))
consumer.consume_line (text, text_len, input.text_before, input.text_after);
consumer.finish (&font_opts);
return consumer.failed ? 1 : 0;
}
protected:
option_parser_t options;
font_options_t font_opts;
text_options_t input;
consumer_t consumer;
};
option_parser_t options ("[FONT-FILE] [TEXT]");
font_opts.add_options (&options);
input.add_options (&options);
consumer.add_options (&options);
options.parse (&argc, &argv);
argc--, argv++;
if (argc && !font_opts.font_file) font_opts.font_file = locale_to_utf8 (argv[0]), argc--, argv++;
if (argc && !input.text && !input.text_file) input.text = locale_to_utf8 (argv[0]), argc--, argv++;
if (argc)
fail (true, "Too many arguments on the command line");
if (!font_opts.font_file)
options.usage ();
if (!input.text && !input.text_file)
input.text_file = g_strdup ("-");
consumer.init (&font_opts);
unsigned int text_len;
const char *text;
while ((text = input.get_line (&text_len, eol)))
consumer.consume_line (text, text_len, input.text_before, input.text_after);
consumer.finish (&font_opts);
return consumer.failed ? 1 : 0;
}
#endif

View File

@ -1,26 +1,17 @@
hb_view_sources = [
'hb-view.cc',
'options.cc',
'ansi-print.cc',
'helper-cairo.cc',
'helper-cairo-ansi.cc',
'view-cairo.cc',
]
hb_shape_sources = [
'hb-shape.cc',
'options.cc',
]
hb_ot_shape_closure_sources = [
'hb-ot-shape-closure.cc',
'options.cc',
]
hb_subset_cli_sources = [
'hb-subset.cc',
'options.cc',
'options-subset.cc',
]
util_deps = [freetype_dep, cairo_dep, cairo_ft_dep, glib_dep]

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,6 @@
#define OPTIONS_HH
#include "hb.hh"
#include "hb-subset.h"
#include <stdlib.h>
#include <stddef.h>
@ -51,28 +50,49 @@
#include <glib.h>
#include <glib/gprintf.h>
void fail (hb_bool_t suggest_help, const char *format, ...) G_GNUC_NORETURN G_GNUC_PRINTF (2, 3);
struct option_group_t
static inline void fail (hb_bool_t suggest_help, const char *format, ...) G_GNUC_NORETURN G_GNUC_PRINTF (2, 3);
static inline void
fail (hb_bool_t suggest_help, const char *format, ...)
{
virtual ~option_group_t () {}
const char *msg;
virtual void add_options (struct option_parser_t *parser) = 0;
va_list vap;
va_start (vap, format);
msg = g_strdup_vprintf (format, vap);
va_end (vap);
const char *prgname = g_get_prgname ();
g_printerr ("%s: %s\n", prgname, msg);
if (suggest_help)
g_printerr ("Try `%s --help' for more information.\n", prgname);
virtual void pre_parse (GError **error G_GNUC_UNUSED) {}
virtual void post_parse (GError **error G_GNUC_UNUSED) {}
};
exit (1);
}
static inline char *
locale_to_utf8 (char *s)
{
char *t;
GError *error = nullptr;
t = g_locale_to_utf8 (s, -1, nullptr, nullptr, &error);
if (!t)
{
fail (true, "Failed converting text to UTF-8");
}
return t;
}
struct option_parser_t
{
option_parser_t (const char *usage)
: usage_str (usage),
context (g_option_context_new (usage)),
to_free (g_ptr_array_new ())
{
memset (this, 0, sizeof (*this));
usage_str = usage;
context = g_option_context_new (usage);
to_free = g_ptr_array_new ();
add_main_options ();
}
@ -87,11 +107,36 @@ struct option_parser_t
void add_main_options ();
static void
post_parse_ (void *thiz, GError **error) {}
template <typename Type>
static auto
post_parse_ (Type *thiz, GError **error) -> decltype (thiz->post_parse (error))
{ thiz->post_parse (error); }
template <typename Type>
static gboolean
post_parse (GOptionContext *context G_GNUC_UNUSED,
GOptionGroup *group G_GNUC_UNUSED,
gpointer data,
GError **error)
{
option_parser_t::post_parse_ (static_cast<Type *> (data), error);
return !*error;
}
template <typename Type>
void add_group (GOptionEntry *entries,
const gchar *name,
const gchar *description,
const gchar *help_description,
option_group_t *option_group);
Type *closure)
{
GOptionGroup *group = g_option_group_new (name, description, help_description,
static_cast<gpointer>(closure), nullptr);
g_option_group_add_entries (group, entries);
g_option_group_set_parse_hooks (group, nullptr, post_parse<Type>);
g_option_context_add_group (context, group);
}
void free_later (char *p) {
g_ptr_array_add (to_free, p);
@ -111,628 +156,68 @@ struct option_parser_t
};
#define DEFAULT_MARGIN 16
#define DEFAULT_FORE "#000000"
#define DEFAULT_BACK "#FFFFFF"
#define FONT_SIZE_UPEM 0x7FFFFFFF
#define FONT_SIZE_NONE 0
struct view_options_t : option_group_t
static inline gchar *
shapers_to_string ()
{
view_options_t (option_parser_t *parser)
{
annotate = false;
fore = nullptr;
back = nullptr;
line_space = 0;
have_font_extents = false;
font_extents.ascent = font_extents.descent = font_extents.line_gap = 0;
margin.t = margin.r = margin.b = margin.l = DEFAULT_MARGIN;
GString *shapers = g_string_new (nullptr);
const char **shaper_list = hb_shape_list_shapers ();
add_options (parser);
}
~view_options_t () override
{
g_free (fore);
g_free (back);
for (; *shaper_list; shaper_list++) {
g_string_append (shapers, *shaper_list);
g_string_append_c (shapers, ',');
}
g_string_truncate (shapers, MAX (0, (gint)shapers->len - 1));
void add_options (option_parser_t *parser) override;
return g_string_free (shapers, false);
}
hb_bool_t annotate;
char *fore;
char *back;
double line_space;
bool have_font_extents;
struct font_extents_t {
double ascent, descent, line_gap;
} font_extents;
struct margin_t {
double t, r, b, l;
} margin;
};
struct shape_options_t : option_group_t
static G_GNUC_NORETURN gboolean
show_version (const char *name G_GNUC_UNUSED,
const char *arg G_GNUC_UNUSED,
gpointer data G_GNUC_UNUSED,
GError **error G_GNUC_UNUSED)
{
shape_options_t (option_parser_t *parser)
{
direction = language = script = nullptr;
bot = eot = preserve_default_ignorables = remove_default_ignorables = false;
features = nullptr;
num_features = 0;
shapers = nullptr;
utf8_clusters = false;
invisible_glyph = 0;
cluster_level = HB_BUFFER_CLUSTER_LEVEL_DEFAULT;
normalize_glyphs = false;
verify = false;
num_iterations = 1;
g_printf ("%s (%s) %s\n", g_get_prgname (), PACKAGE_NAME, PACKAGE_VERSION);
add_options (parser);
}
~shape_options_t () override
{
g_free (direction);
g_free (language);
g_free (script);
free (features);
g_strfreev (shapers);
}
char *shapers = shapers_to_string ();
g_printf ("Available shapers: %s\n", shapers);
g_free (shapers);
if (strcmp (HB_VERSION_STRING, hb_version_string ()))
g_printf ("Linked HarfBuzz library has a different version: %s\n", hb_version_string ());
void add_options (option_parser_t *parser) override;
exit(0);
}
void setup_buffer (hb_buffer_t *buffer)
{
hb_buffer_set_direction (buffer, hb_direction_from_string (direction, -1));
hb_buffer_set_script (buffer, hb_script_from_string (script, -1));
hb_buffer_set_language (buffer, hb_language_from_string (language, -1));
hb_buffer_set_flags (buffer, (hb_buffer_flags_t)
(HB_BUFFER_FLAG_DEFAULT |
(bot ? HB_BUFFER_FLAG_BOT : 0) |
(eot ? HB_BUFFER_FLAG_EOT : 0) |
(preserve_default_ignorables ? HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES : 0) |
(remove_default_ignorables ? HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES : 0) |
0));
hb_buffer_set_invisible_glyph (buffer, invisible_glyph);
hb_buffer_set_cluster_level (buffer, cluster_level);
hb_buffer_guess_segment_properties (buffer);
}
static void copy_buffer_properties (hb_buffer_t *dst, hb_buffer_t *src)
{
hb_segment_properties_t props;
hb_buffer_get_segment_properties (src, &props);
hb_buffer_set_segment_properties (dst, &props);
hb_buffer_set_flags (dst, hb_buffer_get_flags (src));
hb_buffer_set_cluster_level (dst, hb_buffer_get_cluster_level (src));
}
void populate_buffer (hb_buffer_t *buffer, const char *text, int text_len,
const char *text_before, const char *text_after)
{
hb_buffer_clear_contents (buffer);
if (text_before) {
unsigned int len = strlen (text_before);
hb_buffer_add_utf8 (buffer, text_before, len, len, 0);
}
hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len);
if (text_after) {
hb_buffer_add_utf8 (buffer, text_after, -1, 0, 0);
}
if (!utf8_clusters) {
/* Reset cluster values to refer to Unicode character index
* instead of UTF-8 index. */
unsigned int num_glyphs = hb_buffer_get_length (buffer);
hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, nullptr);
for (unsigned int i = 0; i < num_glyphs; i++)
{
info->cluster = i;
info++;
}
}
setup_buffer (buffer);
}
hb_bool_t shape (hb_font_t *font, hb_buffer_t *buffer, const char **error=nullptr)
{
hb_buffer_t *text_buffer = nullptr;
if (verify)
{
text_buffer = hb_buffer_create ();
hb_buffer_append (text_buffer, buffer, 0, -1);
}
if (!hb_shape_full (font, buffer, features, num_features, shapers))
{
if (error)
*error = "all shapers failed.";
goto fail;
}
if (normalize_glyphs)
hb_buffer_normalize_glyphs (buffer);
if (verify && !verify_buffer (buffer, text_buffer, font, error))
goto fail;
if (text_buffer)
hb_buffer_destroy (text_buffer);
return true;
fail:
if (text_buffer)
hb_buffer_destroy (text_buffer);
return false;
}
bool verify_buffer (hb_buffer_t *buffer,
hb_buffer_t *text_buffer,
hb_font_t *font,
const char **error=nullptr)
{
if (!verify_buffer_monotone (buffer, error))
return false;
if (!verify_buffer_safe_to_break (buffer, text_buffer, font, error))
return false;
return true;
}
bool verify_buffer_monotone (hb_buffer_t *buffer, const char **error=nullptr)
{
/* Check that clusters are monotone. */
if (cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES ||
cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS)
{
bool is_forward = HB_DIRECTION_IS_FORWARD (hb_buffer_get_direction (buffer));
unsigned int num_glyphs;
hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &num_glyphs);
for (unsigned int i = 1; i < num_glyphs; i++)
if (info[i-1].cluster != info[i].cluster &&
(info[i-1].cluster < info[i].cluster) != is_forward)
{
if (error)
*error = "clusters are not monotone.";
return false;
}
}
return true;
}
bool verify_buffer_safe_to_break (hb_buffer_t *buffer,
hb_buffer_t *text_buffer,
hb_font_t *font,
const char **error=nullptr)
{
if (cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES &&
cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS)
{
/* Cannot perform this check without monotone clusters.
* Then again, unsafe-to-break flag is much harder to use without
* monotone clusters. */
return true;
}
/* Check that breaking up shaping at safe-to-break is indeed safe. */
hb_buffer_t *fragment = hb_buffer_create ();
hb_buffer_t *reconstruction = hb_buffer_create ();
copy_buffer_properties (reconstruction, buffer);
unsigned int num_glyphs;
hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &num_glyphs);
unsigned int num_chars;
hb_glyph_info_t *text = hb_buffer_get_glyph_infos (text_buffer, &num_chars);
/* Chop text and shape fragments. */
bool forward = HB_DIRECTION_IS_FORWARD (hb_buffer_get_direction (buffer));
unsigned int start = 0;
unsigned int text_start = forward ? 0 : num_chars;
unsigned int text_end = text_start;
for (unsigned int end = 1; end < num_glyphs + 1; end++)
{
if (end < num_glyphs &&
(info[end].cluster == info[end-1].cluster ||
info[end-(forward?0:1)].mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK))
continue;
/* Shape segment corresponding to glyphs start..end. */
if (end == num_glyphs)
{
if (forward)
text_end = num_chars;
else
text_start = 0;
}
else
{
if (forward)
{
unsigned int cluster = info[end].cluster;
while (text_end < num_chars && text[text_end].cluster < cluster)
text_end++;
}
else
{
unsigned int cluster = info[end - 1].cluster;
while (text_start && text[text_start - 1].cluster >= cluster)
text_start--;
}
}
assert (text_start < text_end);
if (0)
printf("start %d end %d text start %d end %d\n", start, end, text_start, text_end);
hb_buffer_clear_contents (fragment);
copy_buffer_properties (fragment, buffer);
/* TODO: Add pre/post context text. */
hb_buffer_flags_t flags = hb_buffer_get_flags (fragment);
if (0 < text_start)
flags = (hb_buffer_flags_t) (flags & ~HB_BUFFER_FLAG_BOT);
if (text_end < num_chars)
flags = (hb_buffer_flags_t) (flags & ~HB_BUFFER_FLAG_EOT);
hb_buffer_set_flags (fragment, flags);
hb_buffer_append (fragment, text_buffer, text_start, text_end);
if (!hb_shape_full (font, fragment, features, num_features, shapers))
{
if (error)
*error = "all shapers failed while shaping fragment.";
hb_buffer_destroy (reconstruction);
hb_buffer_destroy (fragment);
return false;
}
hb_buffer_append (reconstruction, fragment, 0, -1);
start = end;
if (forward)
text_start = text_end;
else
text_end = text_start;
}
bool ret = true;
hb_buffer_diff_flags_t diff = hb_buffer_diff (reconstruction, buffer, (hb_codepoint_t) -1, 0);
if (diff)
{
if (error)
*error = "Safe-to-break test failed.";
ret = false;
/* Return the reconstructed result instead so it can be inspected. */
hb_buffer_set_length (buffer, 0);
hb_buffer_append (buffer, reconstruction, 0, -1);
}
hb_buffer_destroy (reconstruction);
hb_buffer_destroy (fragment);
return ret;
}
void shape_closure (const char *text, int text_len,
hb_font_t *font, hb_buffer_t *buffer,
hb_set_t *glyphs)
{
hb_buffer_reset (buffer);
hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len);
setup_buffer (buffer);
hb_ot_shape_glyphs_closure (font, buffer, features, num_features, glyphs);
}
/* Buffer properties */
char *direction;
char *language;
char *script;
/* Buffer flags */
hb_bool_t bot;
hb_bool_t eot;
hb_bool_t preserve_default_ignorables;
hb_bool_t remove_default_ignorables;
hb_feature_t *features;
unsigned int num_features;
char **shapers;
hb_bool_t utf8_clusters;
hb_codepoint_t invisible_glyph;
hb_buffer_cluster_level_t cluster_level;
hb_bool_t normalize_glyphs;
hb_bool_t verify;
unsigned int num_iterations;
};
struct font_options_t : option_group_t
inline void
option_parser_t::add_main_options ()
{
font_options_t (option_parser_t *parser,
int default_font_size_,
unsigned int subpixel_bits_)
GOptionEntry entries[] =
{
variations = nullptr;
num_variations = 0;
default_font_size = default_font_size_;
x_ppem = 0;
y_ppem = 0;
ptem = 0.;
subpixel_bits = subpixel_bits_;
font_file = nullptr;
face_index = 0;
font_size_x = font_size_y = default_font_size;
font_funcs = nullptr;
ft_load_flags = 2;
{"version", 0, G_OPTION_FLAG_NO_ARG,
G_OPTION_ARG_CALLBACK, (gpointer) &show_version, "Show version numbers", nullptr},
{nullptr}
};
g_option_context_add_main_entries (context, entries, nullptr);
}
blob = nullptr;
font = nullptr;
inline void
option_parser_t::parse (int *argc, char ***argv)
{
setlocale (LC_ALL, "");
add_options (parser);
}
~font_options_t () override
GError *parse_error = nullptr;
if (!g_option_context_parse (context, argc, argv, &parse_error))
{
g_free (font_file);
free (variations);
g_free (font_funcs);
hb_font_destroy (font);
}
void add_options (option_parser_t *parser) override;
hb_font_t *get_font () const;
char *font_file;
mutable hb_blob_t *blob;
unsigned face_index;
hb_variation_t *variations;
unsigned int num_variations;
int default_font_size;
int x_ppem;
int y_ppem;
double ptem;
unsigned int subpixel_bits;
mutable double font_size_x;
mutable double font_size_y;
char *font_funcs;
int ft_load_flags;
private:
mutable hb_font_t *font;
static struct cache_t
{
~cache_t ()
if (parse_error)
{
free ((void *) font_path);
hb_blob_destroy (blob);
hb_face_destroy (face);
fail (true, "%s", parse_error->message);
//g_error_free (parse_error);
}
const char *font_path = nullptr;
hb_blob_t *blob = nullptr;
unsigned face_index = (unsigned) -1;
hb_face_t *face = nullptr;
} cache;
};
struct text_options_t : option_group_t
{
text_options_t (option_parser_t *parser)
{
text_before = nullptr;
text_after = nullptr;
text_len = -1;
text = nullptr;
text_file = nullptr;
fp = nullptr;
gs = nullptr;
line = nullptr;
line_len = UINT_MAX;
add_options (parser);
}
~text_options_t () override
{
g_free (text_before);
g_free (text_after);
g_free (text);
g_free (text_file);
if (gs)
g_string_free (gs, true);
if (fp && fp != stdin)
fclose (fp);
else
fail (true, "Option parse error");
}
}
void add_options (option_parser_t *parser) override;
void post_parse (GError **error G_GNUC_UNUSED) override {
if (text && text_file)
g_set_error (error,
G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Only one of text and text-file can be set");
}
const char *get_line (unsigned int *len, int eol = '\n');
char *text_before;
char *text_after;
int text_len;
char *text;
char *text_file;
private:
FILE *fp;
GString *gs;
char *line;
unsigned int line_len;
};
struct output_options_t : option_group_t
{
output_options_t (option_parser_t *parser,
const char **supported_formats_ = nullptr)
{
output_file = nullptr;
output_format = nullptr;
supported_formats = supported_formats_;
explicit_output_format = false;
fp = nullptr;
add_options (parser);
}
~output_options_t () override
{
g_free (output_file);
g_free (output_format);
if (fp && fp != stdout)
fclose (fp);
}
void add_options (option_parser_t *parser) override;
void post_parse (GError **error G_GNUC_UNUSED) override
{
if (output_format)
explicit_output_format = true;
if (output_file && !output_format) {
output_format = strrchr (output_file, '.');
if (output_format)
{
output_format++; /* skip the dot */
output_format = g_strdup (output_format);
}
}
if (output_file && 0 == strcmp (output_file, "-"))
output_file = nullptr; /* STDOUT */
}
FILE *get_file_handle ();
char *output_file;
char *output_format;
const char **supported_formats;
bool explicit_output_format;
mutable FILE *fp;
};
struct format_options_t : option_group_t
{
format_options_t (option_parser_t *parser) {
show_glyph_names = true;
show_positions = true;
show_advances = true;
show_clusters = true;
show_text = false;
show_unicode = false;
show_line_num = false;
show_extents = false;
show_flags = false;
trace = false;
add_options (parser);
}
void add_options (option_parser_t *parser) override;
void serialize (hb_buffer_t *buffer,
hb_font_t *font,
hb_buffer_serialize_format_t format,
hb_buffer_serialize_flags_t flags,
GString *gs);
void serialize_line_no (unsigned int line_no,
GString *gs);
void serialize_buffer_of_text (hb_buffer_t *buffer,
unsigned int line_no,
const char *text,
unsigned int text_len,
hb_font_t *font,
GString *gs);
void serialize_message (unsigned int line_no,
const char *type,
const char *msg,
GString *gs);
void serialize_buffer_of_glyphs (hb_buffer_t *buffer,
unsigned int line_no,
const char *text,
unsigned int text_len,
hb_font_t *font,
hb_buffer_serialize_format_t output_format,
hb_buffer_serialize_flags_t format_flags,
GString *gs);
hb_bool_t show_glyph_names;
hb_bool_t show_positions;
hb_bool_t show_advances;
hb_bool_t show_clusters;
hb_bool_t show_text;
hb_bool_t show_unicode;
hb_bool_t show_line_num;
hb_bool_t show_extents;
hb_bool_t show_flags;
hb_bool_t trace;
};
struct subset_options_t : option_group_t
{
subset_options_t (option_parser_t *parser)
{
input = hb_subset_input_create_or_fail ();
num_iterations = 1;
add_options (parser);
}
~subset_options_t () override
{
hb_subset_input_destroy (input);
}
void add_options (option_parser_t *parser) override;
hb_bool_t* bool_for(hb_subset_flags_t flag)
{
for (unsigned i = 0; i < sizeof(int) * 8; i++)
{
if (1u << i == flag)
return &flags[i];
}
return &flags[sizeof(int) * 8 - 1];
}
unsigned num_iterations;
hb_subset_input_t * get_input ()
{
hb_subset_flags_t flags_set = HB_SUBSET_FLAGS_DEFAULT;
for (unsigned i = 0; i < sizeof(int) * 8; i++)
{
if (flags[i])
flags_set = (hb_subset_flags_t) (flags_set | (1u << i));
}
hb_subset_input_set_flags (input, flags_set);
return input;
}
hb_bool_t flags[sizeof(int) * 8] = {0};
hb_subset_input_t *input;
};
/* fallback implementation for scalbn()/scalbnf() for pre-2013 MSVC */
#if defined (_MSC_VER) && (_MSC_VER < 1800)

123
util/output-options.hh Normal file
View File

@ -0,0 +1,123 @@
/*
* Copyright © 2011 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Google Author(s): Behdad Esfahbod
*/
#ifndef OUTPUT_OPTIONS_HH
#define OUTPUT_OPTIONS_HH
#include "options.hh"
struct output_options_t
{
~output_options_t ()
{
g_free (output_file);
g_free (output_format);
if (fp && fp != stdout)
fclose (fp);
}
void add_options (option_parser_t *parser,
const char **supported_formats = nullptr);
void post_parse (GError **error G_GNUC_UNUSED)
{
if (output_format)
explicit_output_format = true;
if (output_file && !output_format) {
output_format = strrchr (output_file, '.');
if (output_format)
{
output_format++; /* skip the dot */
output_format = g_strdup (output_format);
}
}
if (output_file && 0 == strcmp (output_file, "-"))
output_file = nullptr; /* STDOUT */
}
FILE *get_file_handle ();
char *output_file = nullptr;
char *output_format = nullptr;
bool explicit_output_format = false;
mutable FILE *fp = nullptr;
};
FILE *
output_options_t::get_file_handle ()
{
if (fp)
return fp;
if (output_file)
fp = fopen (output_file, "wb");
else {
#if defined(_WIN32) || defined(__CYGWIN__)
setmode (fileno (stdout), O_BINARY);
#endif
fp = stdout;
}
if (!fp)
fail (false, "Cannot open output file `%s': %s",
g_filename_display_name (output_file), strerror (errno));
return fp;
}
void
output_options_t::add_options (option_parser_t *parser,
const char **supported_formats)
{
const char *text = nullptr;
if (supported_formats)
{
char *items = g_strjoinv ("/", const_cast<char **> (supported_formats));
text = g_strdup_printf ("Set output format\n\n Supported output formats are: %s", items);
g_free (items);
parser->free_later ((char *) text);
}
GOptionEntry entries[] =
{
{"output-file", 'o', 0, G_OPTION_ARG_STRING, &this->output_file, "Set output file-name (default: stdout)","filename"},
{"output-format", 'O', supported_formats ? 0 : G_OPTION_FLAG_HIDDEN,
G_OPTION_ARG_STRING, &this->output_format, text, "format"},
{nullptr}
};
parser->add_group (entries,
"output",
"Output destination & format options:",
"Options for the destination & form of the output",
this);
}
#endif

View File

@ -27,26 +27,24 @@
#ifndef HB_SHAPE_CONSUMER_HH
#define HB_SHAPE_CONSUMER_HH
#include "hb.hh"
#include "options.hh"
#include "font-options.hh"
#include "shape-options.hh"
template <typename output_t>
struct shape_consumer_t
struct shape_consumer_t : shape_options_t
{
shape_consumer_t (option_parser_t *parser)
: failed (false),
shaper (parser),
output (parser),
font (nullptr),
buffer (nullptr) {}
void add_options (option_parser_t *parser)
{
shape_options_t::add_options (parser);
output.add_options (parser);
}
void init (hb_buffer_t *buffer_,
const font_options_t *font_opts)
void init (const font_options_t *font_opts)
{
font = hb_font_reference (font_opts->get_font ());
failed = false;
buffer = hb_buffer_reference (buffer_);
buffer = hb_buffer_create ();
output.init (buffer, font_opts);
}
@ -57,14 +55,14 @@ struct shape_consumer_t
{
output.new_line ();
for (unsigned int n = shaper.num_iterations; n; n--)
for (unsigned int n = num_iterations; n; n--)
{
const char *error = nullptr;
shaper.populate_buffer (buffer, text, text_len, text_before, text_after);
populate_buffer (buffer, text, text_len, text_before, text_after);
if (n == 1)
output.consume_text (buffer, text, text_len, shaper.utf8_clusters);
if (!shaper.shape (font, buffer, &error))
output.consume_text (buffer, text, text_len, utf8_clusters);
if (!shape (font, buffer, &error))
{
failed = true;
output.error (error);
@ -75,7 +73,7 @@ struct shape_consumer_t
}
}
output.consume_glyphs (buffer, text, text_len, shaper.utf8_clusters);
output.consume_glyphs (buffer, text, text_len, utf8_clusters);
}
void finish (const font_options_t *font_opts)
{
@ -87,14 +85,13 @@ struct shape_consumer_t
}
public:
bool failed;
bool failed = false;
protected:
shape_options_t shaper;
output_t output;
hb_font_t *font;
hb_buffer_t *buffer;
hb_font_t *font = nullptr;
hb_buffer_t *buffer = nullptr;
};

214
util/shape-format.hh Normal file
View File

@ -0,0 +1,214 @@
/*
* Copyright © 2011 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Google Author(s): Behdad Esfahbod
*/
#ifndef SHAPE_FORMAT_OPTIONS_HH
#define SHAPE_FORMAT_OPTIONS_HH
#include "options.hh"
struct shape_format_options_t
{
void add_options (option_parser_t *parser);
void serialize (hb_buffer_t *buffer,
hb_font_t *font,
hb_buffer_serialize_format_t format,
hb_buffer_serialize_flags_t flags,
GString *gs);
void serialize_line_no (unsigned int line_no,
GString *gs);
void serialize_buffer_of_text (hb_buffer_t *buffer,
unsigned int line_no,
const char *text,
unsigned int text_len,
hb_font_t *font,
GString *gs);
void serialize_message (unsigned int line_no,
const char *type,
const char *msg,
GString *gs);
void serialize_buffer_of_glyphs (hb_buffer_t *buffer,
unsigned int line_no,
const char *text,
unsigned int text_len,
hb_font_t *font,
hb_buffer_serialize_format_t output_format,
hb_buffer_serialize_flags_t format_flags,
GString *gs);
hb_bool_t show_glyph_names = true;
hb_bool_t show_positions = true;
hb_bool_t show_advances = true;
hb_bool_t show_clusters = true;
hb_bool_t show_text = false;
hb_bool_t show_unicode = false;
hb_bool_t show_line_num = false;
hb_bool_t show_extents = false;
hb_bool_t show_flags = false;
hb_bool_t trace = false;
};
static gboolean
parse_verbose (const char *name G_GNUC_UNUSED,
const char *arg G_GNUC_UNUSED,
gpointer data G_GNUC_UNUSED,
GError **error G_GNUC_UNUSED)
{
shape_format_options_t *format_opts = (shape_format_options_t *) data;
format_opts->show_text = format_opts->show_unicode = format_opts->show_line_num = true;
return true;
}
static gboolean
parse_ned (const char *name G_GNUC_UNUSED,
const char *arg G_GNUC_UNUSED,
gpointer data G_GNUC_UNUSED,
GError **error G_GNUC_UNUSED)
{
shape_format_options_t *format_opts = (shape_format_options_t *) data;
format_opts->show_clusters = format_opts->show_advances = false;
return true;
}
inline void
shape_format_options_t::serialize (hb_buffer_t *buffer,
hb_font_t *font,
hb_buffer_serialize_format_t output_format,
hb_buffer_serialize_flags_t flags,
GString *gs)
{
unsigned int num_glyphs = hb_buffer_get_length (buffer);
unsigned int start = 0;
while (start < num_glyphs)
{
char buf[32768];
unsigned int consumed;
start += hb_buffer_serialize (buffer, start, num_glyphs,
buf, sizeof (buf), &consumed,
font, output_format, flags);
if (!consumed)
break;
g_string_append (gs, buf);
}
}
inline void
shape_format_options_t::serialize_line_no (unsigned int line_no,
GString *gs)
{
if (show_line_num)
g_string_append_printf (gs, "%d: ", line_no);
}
inline void
shape_format_options_t::serialize_buffer_of_text (hb_buffer_t *buffer,
unsigned int line_no,
const char *text,
unsigned int text_len,
hb_font_t *font,
GString *gs)
{
if (show_text)
{
serialize_line_no (line_no, gs);
g_string_append_c (gs, '(');
g_string_append_len (gs, text, text_len);
g_string_append_c (gs, ')');
g_string_append_c (gs, '\n');
}
if (show_unicode)
{
serialize_line_no (line_no, gs);
serialize (buffer, font, HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT, gs);
g_string_append_c (gs, '\n');
}
}
inline void
shape_format_options_t::serialize_message (unsigned int line_no,
const char *type,
const char *msg,
GString *gs)
{
serialize_line_no (line_no, gs);
g_string_append_printf (gs, "%s: %s", type, msg);
g_string_append_c (gs, '\n');
}
inline void
shape_format_options_t::serialize_buffer_of_glyphs (hb_buffer_t *buffer,
unsigned int line_no,
const char *text,
unsigned int text_len,
hb_font_t *font,
hb_buffer_serialize_format_t output_format,
hb_buffer_serialize_flags_t format_flags,
GString *gs)
{
serialize_line_no (line_no, gs);
serialize (buffer, font, output_format, format_flags, gs);
g_string_append_c (gs, '\n');
}
void
shape_format_options_t::add_options (option_parser_t *parser)
{
GOptionEntry entries[] =
{
{"show-text", 0, 0, G_OPTION_ARG_NONE, &this->show_text, "Prefix each line of output with its corresponding input text", nullptr},
{"show-unicode", 0, 0, G_OPTION_ARG_NONE, &this->show_unicode, "Prefix each line of output with its corresponding input codepoint(s)", nullptr},
{"show-line-num", 0, 0, G_OPTION_ARG_NONE, &this->show_line_num, "Prefix each line of output with its corresponding input line number", nullptr},
{"verbose", 'v', G_OPTION_FLAG_NO_ARG,
G_OPTION_ARG_CALLBACK, (gpointer) &parse_verbose, "Prefix each line of output with all of the above", nullptr},
{"no-glyph-names", 0, G_OPTION_FLAG_REVERSE,
G_OPTION_ARG_NONE, &this->show_glyph_names, "Output glyph indices instead of names", nullptr},
{"no-positions", 0, G_OPTION_FLAG_REVERSE,
G_OPTION_ARG_NONE, &this->show_positions, "Do not output glyph positions", nullptr},
{"no-advances", 0, G_OPTION_FLAG_REVERSE,
G_OPTION_ARG_NONE, &this->show_advances, "Do not output glyph advances", nullptr},
{"no-clusters", 0, G_OPTION_FLAG_REVERSE,
G_OPTION_ARG_NONE, &this->show_clusters, "Do not output cluster indices", nullptr},
{"show-extents", 0, 0, G_OPTION_ARG_NONE, &this->show_extents, "Output glyph extents", nullptr},
{"show-flags", 0, 0, G_OPTION_ARG_NONE, &this->show_flags, "Output glyph flags", nullptr},
{"ned", 'v', G_OPTION_FLAG_NO_ARG,
G_OPTION_ARG_CALLBACK, (gpointer) &parse_ned, "No Extra Data; Do not output clusters or advances", nullptr},
{"trace", 'V', 0, G_OPTION_ARG_NONE, &this->trace, "Output interim shaping results", nullptr},
{nullptr}
};
parser->add_group (entries,
"output-syntax",
"Output syntax:\n"
" text: [<glyph name or index>=<glyph cluster index within input>@<horizontal displacement>,<vertical displacement>+<horizontal advance>,<vertical advance>|...]\n"
" json: [{\"g\": <glyph name or index>, \"ax\": <horizontal advance>, \"ay\": <vertical advance>, \"dx\": <horizontal displacement>, \"dy\": <vertical displacement>, \"cl\": <glyph cluster index within input>}, ...]\n"
"\nOutput syntax options:",
"Options for the syntax of the output",
this);
}
#endif

490
util/shape-options.hh Normal file
View File

@ -0,0 +1,490 @@
/*
* Copyright © 2011 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Google Author(s): Behdad Esfahbod
*/
#ifndef SHAPE_OPTIONS_HH
#define SHAPE_OPTIONS_HH
#include "options.hh"
struct shape_options_t
{
~shape_options_t ()
{
g_free (direction);
g_free (language);
g_free (script);
free (features);
g_strfreev (shapers);
}
void add_options (option_parser_t *parser);
void setup_buffer (hb_buffer_t *buffer)
{
hb_buffer_set_direction (buffer, hb_direction_from_string (direction, -1));
hb_buffer_set_script (buffer, hb_script_from_string (script, -1));
hb_buffer_set_language (buffer, hb_language_from_string (language, -1));
hb_buffer_set_flags (buffer, (hb_buffer_flags_t)
(HB_BUFFER_FLAG_DEFAULT |
(bot ? HB_BUFFER_FLAG_BOT : 0) |
(eot ? HB_BUFFER_FLAG_EOT : 0) |
(preserve_default_ignorables ? HB_BUFFER_FLAG_PRESERVE_DEFAULT_IGNORABLES : 0) |
(remove_default_ignorables ? HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES : 0) |
0));
hb_buffer_set_invisible_glyph (buffer, invisible_glyph);
hb_buffer_set_cluster_level (buffer, cluster_level);
hb_buffer_guess_segment_properties (buffer);
}
static void copy_buffer_properties (hb_buffer_t *dst, hb_buffer_t *src)
{
hb_segment_properties_t props;
hb_buffer_get_segment_properties (src, &props);
hb_buffer_set_segment_properties (dst, &props);
hb_buffer_set_flags (dst, hb_buffer_get_flags (src));
hb_buffer_set_cluster_level (dst, hb_buffer_get_cluster_level (src));
}
void populate_buffer (hb_buffer_t *buffer, const char *text, int text_len,
const char *text_before, const char *text_after)
{
hb_buffer_clear_contents (buffer);
if (text_before) {
unsigned int len = strlen (text_before);
hb_buffer_add_utf8 (buffer, text_before, len, len, 0);
}
hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len);
if (text_after) {
hb_buffer_add_utf8 (buffer, text_after, -1, 0, 0);
}
if (!utf8_clusters) {
/* Reset cluster values to refer to Unicode character index
* instead of UTF-8 index. */
unsigned int num_glyphs = hb_buffer_get_length (buffer);
hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, nullptr);
for (unsigned int i = 0; i < num_glyphs; i++)
{
info->cluster = i;
info++;
}
}
setup_buffer (buffer);
}
hb_bool_t shape (hb_font_t *font, hb_buffer_t *buffer, const char **error=nullptr)
{
hb_buffer_t *text_buffer = nullptr;
if (verify)
{
text_buffer = hb_buffer_create ();
hb_buffer_append (text_buffer, buffer, 0, -1);
}
if (!hb_shape_full (font, buffer, features, num_features, shapers))
{
if (error)
*error = "all shapers failed.";
goto fail;
}
if (normalize_glyphs)
hb_buffer_normalize_glyphs (buffer);
if (verify && !verify_buffer (buffer, text_buffer, font, error))
goto fail;
if (text_buffer)
hb_buffer_destroy (text_buffer);
return true;
fail:
if (text_buffer)
hb_buffer_destroy (text_buffer);
return false;
}
bool verify_buffer (hb_buffer_t *buffer,
hb_buffer_t *text_buffer,
hb_font_t *font,
const char **error=nullptr)
{
if (!verify_buffer_monotone (buffer, error))
return false;
if (!verify_buffer_safe_to_break (buffer, text_buffer, font, error))
return false;
return true;
}
bool verify_buffer_monotone (hb_buffer_t *buffer, const char **error=nullptr)
{
/* Check that clusters are monotone. */
if (cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES ||
cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS)
{
bool is_forward = HB_DIRECTION_IS_FORWARD (hb_buffer_get_direction (buffer));
unsigned int num_glyphs;
hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &num_glyphs);
for (unsigned int i = 1; i < num_glyphs; i++)
if (info[i-1].cluster != info[i].cluster &&
(info[i-1].cluster < info[i].cluster) != is_forward)
{
if (error)
*error = "clusters are not monotone.";
return false;
}
}
return true;
}
bool verify_buffer_safe_to_break (hb_buffer_t *buffer,
hb_buffer_t *text_buffer,
hb_font_t *font,
const char **error=nullptr)
{
if (cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES &&
cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS)
{
/* Cannot perform this check without monotone clusters.
* Then again, unsafe-to-break flag is much harder to use without
* monotone clusters. */
return true;
}
/* Check that breaking up shaping at safe-to-break is indeed safe. */
hb_buffer_t *fragment = hb_buffer_create ();
hb_buffer_t *reconstruction = hb_buffer_create ();
copy_buffer_properties (reconstruction, buffer);
unsigned int num_glyphs;
hb_glyph_info_t *info = hb_buffer_get_glyph_infos (buffer, &num_glyphs);
unsigned int num_chars;
hb_glyph_info_t *text = hb_buffer_get_glyph_infos (text_buffer, &num_chars);
/* Chop text and shape fragments. */
bool forward = HB_DIRECTION_IS_FORWARD (hb_buffer_get_direction (buffer));
unsigned int start = 0;
unsigned int text_start = forward ? 0 : num_chars;
unsigned int text_end = text_start;
for (unsigned int end = 1; end < num_glyphs + 1; end++)
{
if (end < num_glyphs &&
(info[end].cluster == info[end-1].cluster ||
info[end-(forward?0:1)].mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK))
continue;
/* Shape segment corresponding to glyphs start..end. */
if (end == num_glyphs)
{
if (forward)
text_end = num_chars;
else
text_start = 0;
}
else
{
if (forward)
{
unsigned int cluster = info[end].cluster;
while (text_end < num_chars && text[text_end].cluster < cluster)
text_end++;
}
else
{
unsigned int cluster = info[end - 1].cluster;
while (text_start && text[text_start - 1].cluster >= cluster)
text_start--;
}
}
assert (text_start < text_end);
if (0)
printf("start %d end %d text start %d end %d\n", start, end, text_start, text_end);
hb_buffer_clear_contents (fragment);
copy_buffer_properties (fragment, buffer);
/* TODO: Add pre/post context text. */
hb_buffer_flags_t flags = hb_buffer_get_flags (fragment);
if (0 < text_start)
flags = (hb_buffer_flags_t) (flags & ~HB_BUFFER_FLAG_BOT);
if (text_end < num_chars)
flags = (hb_buffer_flags_t) (flags & ~HB_BUFFER_FLAG_EOT);
hb_buffer_set_flags (fragment, flags);
hb_buffer_append (fragment, text_buffer, text_start, text_end);
if (!hb_shape_full (font, fragment, features, num_features, shapers))
{
if (error)
*error = "all shapers failed while shaping fragment.";
hb_buffer_destroy (reconstruction);
hb_buffer_destroy (fragment);
return false;
}
hb_buffer_append (reconstruction, fragment, 0, -1);
start = end;
if (forward)
text_start = text_end;
else
text_end = text_start;
}
bool ret = true;
hb_buffer_diff_flags_t diff = hb_buffer_diff (reconstruction, buffer, (hb_codepoint_t) -1, 0);
if (diff)
{
if (error)
*error = "Safe-to-break test failed.";
ret = false;
/* Return the reconstructed result instead so it can be inspected. */
hb_buffer_set_length (buffer, 0);
hb_buffer_append (buffer, reconstruction, 0, -1);
}
hb_buffer_destroy (reconstruction);
hb_buffer_destroy (fragment);
return ret;
}
void shape_closure (const char *text, int text_len,
hb_font_t *font, hb_buffer_t *buffer,
hb_set_t *glyphs)
{
hb_buffer_reset (buffer);
hb_buffer_add_utf8 (buffer, text, text_len, 0, text_len);
setup_buffer (buffer);
hb_ot_shape_glyphs_closure (font, buffer, features, num_features, glyphs);
}
/* Buffer properties */
char *direction = nullptr;
char *language = nullptr;
char *script = nullptr;
/* Buffer flags */
hb_bool_t bot = false;
hb_bool_t eot = false;
hb_bool_t preserve_default_ignorables = false;
hb_bool_t remove_default_ignorables = false;
hb_feature_t *features = nullptr;
unsigned int num_features = 0;
char **shapers = nullptr;
hb_bool_t utf8_clusters = false;
hb_codepoint_t invisible_glyph = 0;
hb_buffer_cluster_level_t cluster_level = HB_BUFFER_CLUSTER_LEVEL_DEFAULT;
hb_bool_t normalize_glyphs = false;
hb_bool_t verify = false;
unsigned int num_iterations = 1;
};
static gboolean
parse_shapers (const char *name G_GNUC_UNUSED,
const char *arg,
gpointer data,
GError **error)
{
shape_options_t *shape_opts = (shape_options_t *) data;
char **shapers = g_strsplit (arg, ",", 0);
for (char **shaper = shapers; *shaper; shaper++) {
bool found = false;
for (const char **hb_shaper = hb_shape_list_shapers (); *hb_shaper; hb_shaper++) {
if (strcmp (*shaper, *hb_shaper) == 0) {
found = true;
break;
}
}
if (!found) {
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Unknown or unsupported shaper: %s", *shaper);
g_strfreev (shapers);
return false;
}
}
g_strfreev (shape_opts->shapers);
shape_opts->shapers = shapers;
return true;
}
static G_GNUC_NORETURN gboolean
list_shapers (const char *name G_GNUC_UNUSED,
const char *arg G_GNUC_UNUSED,
gpointer data G_GNUC_UNUSED,
GError **error G_GNUC_UNUSED)
{
for (const char **shaper = hb_shape_list_shapers (); *shaper; shaper++)
g_printf ("%s\n", *shaper);
exit(0);
}
static gboolean
parse_features (const char *name G_GNUC_UNUSED,
const char *arg,
gpointer data,
GError **error G_GNUC_UNUSED)
{
shape_options_t *shape_opts = (shape_options_t *) data;
char *s = (char *) arg;
size_t l = strlen (s);
char *p;
shape_opts->num_features = 0;
g_free (shape_opts->features);
shape_opts->features = nullptr;
/* if the string is quoted, strip the quotes */
if (s[0] == s[l - 1] && (s[0] == '\"' || s[0] == '\''))
{
s[l - 1] = '\0';
s++;
}
if (!*s)
return true;
/* count the features first, so we can allocate memory */
p = s;
do {
shape_opts->num_features++;
p = strchr (p, ',');
if (p)
p++;
} while (p);
shape_opts->features = (hb_feature_t *) calloc (shape_opts->num_features, sizeof (*shape_opts->features));
if (!shape_opts->features)
return false;
/* now do the actual parsing */
p = s;
shape_opts->num_features = 0;
while (p && *p) {
char *end = strchr (p, ',');
if (hb_feature_from_string (p, end ? end - p : -1, &shape_opts->features[shape_opts->num_features]))
shape_opts->num_features++;
p = end ? end + 1 : nullptr;
}
return true;
}
void
shape_options_t::add_options (option_parser_t *parser)
{
GOptionEntry entries[] =
{
{"list-shapers", 0, G_OPTION_FLAG_NO_ARG,
G_OPTION_ARG_CALLBACK, (gpointer) &list_shapers, "List available shapers and quit", nullptr},
{"shaper", 0, G_OPTION_FLAG_HIDDEN,
G_OPTION_ARG_CALLBACK, (gpointer) &parse_shapers, "Hidden duplicate of --shapers", nullptr},
{"shapers", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_shapers, "Set comma-separated list of shapers to try","list"},
{"direction", 0, 0, G_OPTION_ARG_STRING, &this->direction, "Set text direction (default: auto)", "ltr/rtl/ttb/btt"},
{"language", 0, 0, G_OPTION_ARG_STRING, &this->language, "Set text language (default: $LANG)", "langstr"},
{"script", 0, 0, G_OPTION_ARG_STRING, &this->script, "Set text script (default: auto)", "ISO-15924 tag"},
{"bot", 0, 0, G_OPTION_ARG_NONE, &this->bot, "Treat text as beginning-of-paragraph", nullptr},
{"eot", 0, 0, G_OPTION_ARG_NONE, &this->eot, "Treat text as end-of-paragraph", nullptr},
{"preserve-default-ignorables",0, 0, G_OPTION_ARG_NONE, &this->preserve_default_ignorables, "Preserve Default-Ignorable characters", nullptr},
{"remove-default-ignorables",0, 0, G_OPTION_ARG_NONE, &this->remove_default_ignorables, "Remove Default-Ignorable characters", nullptr},
{"invisible-glyph", 0, 0, G_OPTION_ARG_INT, &this->invisible_glyph, "Glyph value to replace Default-Ignorables with", nullptr},
{"utf8-clusters", 0, 0, G_OPTION_ARG_NONE, &this->utf8_clusters, "Use UTF8 byte indices, not char indices", nullptr},
{"cluster-level", 0, 0, G_OPTION_ARG_INT, &this->cluster_level, "Cluster merging level (default: 0)", "0/1/2"},
{"normalize-glyphs",0, 0, G_OPTION_ARG_NONE, &this->normalize_glyphs, "Rearrange glyph clusters in nominal order", nullptr},
{"verify", 0, 0, G_OPTION_ARG_NONE, &this->verify, "Perform sanity checks on shaping results", nullptr},
{"num-iterations", 'n', 0, G_OPTION_ARG_INT, &this->num_iterations, "Run shaper N times (default: 1)", "N"},
{nullptr}
};
parser->add_group (entries,
"shape",
"Shape options:",
"Options for the shaping process",
this);
const gchar *features_help = "Comma-separated list of font features\n"
"\n"
" Features can be enabled or disabled, either globally or limited to\n"
" specific character ranges. The format for specifying feature settings\n"
" follows. All valid CSS font-feature-settings values other than 'normal'\n"
" and the global values are also accepted, though not documented below.\n"
" CSS string escapes are not supported."
"\n"
" The range indices refer to the positions between Unicode characters,\n"
" unless the --utf8-clusters is provided, in which case range indices\n"
" refer to UTF-8 byte indices. The position before the first character\n"
" is always 0.\n"
"\n"
" The format is Python-esque. Here is how it all works:\n"
"\n"
" Syntax: Value: Start: End:\n"
"\n"
" Setting value:\n"
" \"kern\" 1 0 ∞ # Turn feature on\n"
" \"+kern\" 1 0 ∞ # Turn feature on\n"
" \"-kern\" 0 0 ∞ # Turn feature off\n"
" \"kern=0\" 0 0 ∞ # Turn feature off\n"
" \"kern=1\" 1 0 ∞ # Turn feature on\n"
" \"aalt=2\" 2 0 ∞ # Choose 2nd alternate\n"
"\n"
" Setting index:\n"
" \"kern[]\" 1 0 ∞ # Turn feature on\n"
" \"kern[:]\" 1 0 ∞ # Turn feature on\n"
" \"kern[5:]\" 1 5 ∞ # Turn feature on, partial\n"
" \"kern[:5]\" 1 0 5 # Turn feature on, partial\n"
" \"kern[3:5]\" 1 3 5 # Turn feature on, range\n"
" \"kern[3]\" 1 3 3+1 # Turn feature on, single char\n"
"\n"
" Mixing it all:\n"
"\n"
" \"aalt[3:5]=2\" 2 3 5 # Turn 2nd alternate on for range";
GOptionEntry entries2[] =
{
{"features", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_features, features_help, "list"},
{nullptr}
};
parser->add_group (entries2,
"features",
"Features options:",
"Options for font features used",
this);
}
#endif

View File

@ -24,9 +24,51 @@
* Google Author(s): Garret Rieger
*/
#include "options.hh"
#ifndef SUBSET_OPTIONS_HH
#define SUBSET_OPTIONS_HH
#include "options.hh"
#include "hb-subset.h"
struct subset_options_t
{
subset_options_t ()
: input (hb_subset_input_create_or_fail ())
{}
~subset_options_t ()
{
hb_subset_input_destroy (input);
}
void add_options (option_parser_t *parser);
hb_bool_t* bool_for(hb_subset_flags_t flag)
{
for (unsigned i = 0; i < sizeof(int) * 8; i++)
{
if (1u << i == flag)
return &flags[i];
}
return &flags[sizeof(int) * 8 - 1];
}
hb_subset_input_t * get_input ()
{
hb_subset_flags_t flags_set = HB_SUBSET_FLAGS_DEFAULT;
for (unsigned i = 0; i < sizeof(int) * 8; i++)
{
if (flags[i])
flags_set = (hb_subset_flags_t) (flags_set | (1u << i));
}
hb_subset_input_set_flags (input, flags_set);
return input;
}
unsigned num_iterations = 1;
hb_subset_input_t *input = nullptr;
hb_bool_t flags[sizeof(int) * 8] = {0};
};
#include "hb-subset-input.hh"
static gboolean
parse_gids (const char *name G_GNUC_UNUSED,
@ -35,7 +77,7 @@ parse_gids (const char *name G_GNUC_UNUSED,
GError **error G_GNUC_UNUSED)
{
subset_options_t *subset_opts = (subset_options_t *) data;
hb_set_t *gids = subset_opts->input->glyphs;
hb_set_t *gids = hb_subset_input_glyph_set (subset_opts->input);
char *s = (char *) arg;
char *p;
@ -95,7 +137,7 @@ parse_nameids (const char *name,
GError **error G_GNUC_UNUSED)
{
subset_options_t *subset_opts = (subset_options_t *) data;
hb_set_t *name_ids = subset_opts->input->name_ids;
hb_set_t *name_ids = hb_subset_input_nameid_set (subset_opts->input);
char last_name_char = name[strlen (name) - 1];
@ -151,7 +193,7 @@ parse_name_languages (const char *name,
GError **error G_GNUC_UNUSED)
{
subset_options_t *subset_opts = (subset_options_t *) data;
hb_set_t *name_languages = subset_opts->input->name_languages;
hb_set_t *name_languages = hb_subset_input_namelangid_set (subset_opts->input);
char last_name_char = name[strlen (name) - 1];
@ -207,7 +249,7 @@ parse_layout_features (const char *name,
GError **error G_GNUC_UNUSED)
{
subset_options_t *subset_opts = (subset_options_t *) data;
hb_set_t *layout_features = subset_opts->input->layout_features;
hb_set_t *layout_features = hb_subset_input_layout_features_set (subset_opts->input);
char last_name_char = name[strlen (name) - 1];
@ -256,7 +298,7 @@ parse_drop_tables (const char *name,
GError **error G_GNUC_UNUSED)
{
subset_options_t *subset_opts = (subset_options_t *) data;
hb_set_t *drop_tables = subset_opts->input->drop_tables;
hb_set_t *drop_tables = hb_subset_input_drop_tables_set (subset_opts->input);
char last_name_char = name[strlen (name) - 1];
@ -324,3 +366,5 @@ subset_options_t::add_options (option_parser_t *parser)
"Options subsetting",
this);
}
#endif

211
util/text-options.hh Normal file
View File

@ -0,0 +1,211 @@
struct text_options_t
{
~text_options_t ()
{
g_free (text_before);
g_free (text_after);
g_free (text);
g_free (text_file);
if (gs)
g_string_free (gs, true);
if (fp && fp != stdin)
fclose (fp);
}
void add_options (option_parser_t *parser);
void post_parse (GError **error G_GNUC_UNUSED)
{
if (text && text_file)
g_set_error (error,
G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Only one of text and text-file can be set");
}
const char *get_line (unsigned int *len, int eol = '\n');
char *text_before = nullptr;
char *text_after = nullptr;
int text_len = -1;
char *text = nullptr;
char *text_file = nullptr;
private:
FILE *fp = nullptr;
GString *gs = nullptr;
char *line = nullptr;
unsigned int line_len = UINT_MAX;
};
static gboolean
parse_text (const char *name G_GNUC_UNUSED,
const char *arg,
gpointer data,
GError **error G_GNUC_UNUSED)
{
text_options_t *text_opts = (text_options_t *) data;
if (text_opts->text)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Either --text or --unicodes can be provided but not both");
return false;
}
text_opts->text_len = -1;
text_opts->text = g_strdup (arg);
return true;
}
static gboolean
parse_unicodes (const char *name G_GNUC_UNUSED,
const char *arg,
gpointer data,
GError **error G_GNUC_UNUSED)
{
text_options_t *text_opts = (text_options_t *) data;
if (text_opts->text)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Either --text or --unicodes can be provided but not both");
return false;
}
GString *gs = g_string_new (nullptr);
if (0 == strcmp (arg, "*"))
{
g_string_append_c (gs, '*');
}
else
{
char *s = (char *) arg;
char *p;
while (s && *s)
{
#define DELIMITERS "<+>{},;&#\\xXuUnNiI\n\t\v\f\r "
while (*s && strchr (DELIMITERS, *s))
s++;
if (!*s)
break;
errno = 0;
hb_codepoint_t u = strtoul (s, &p, 16);
if (errno || s == p)
{
g_string_free (gs, TRUE);
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Failed parsing Unicode values at: '%s'", s);
return false;
}
g_string_append_unichar (gs, u);
s = p;
}
}
text_opts->text_len = gs->len;
text_opts->text = g_string_free (gs, FALSE);
return true;
}
const char *
text_options_t::get_line (unsigned int *len, int eol)
{
if (text)
{
if (!line)
{
line = text;
line_len = text_len;
}
if (line_len == UINT_MAX)
line_len = strlen (line);
if (!line_len) {
*len = 0;
return nullptr;
}
const char *ret = line;
const char *p = (const char *) memchr (line, eol, line_len);
unsigned int ret_len;
if (!p)
{
ret_len = line_len;
line += ret_len;
line_len = 0;
}
else
{
ret_len = p - ret;
line += ret_len + 1;
line_len -= ret_len + 1;
}
*len = ret_len;
return ret;
}
if (!fp)
{
if (!text_file)
fail (true, "At least one of text or text-file must be set");
if (0 != strcmp (text_file, "-"))
fp = fopen (text_file, "r");
else
fp = stdin;
if (!fp)
fail (false, "Failed opening text file `%s': %s",
text_file, strerror (errno));
gs = g_string_new (nullptr);
}
g_string_set_size (gs, 0);
char buf[BUFSIZ];
while (fgets (buf, sizeof (buf), fp))
{
unsigned bytes = strlen (buf);
if (bytes && (int) (unsigned char) buf[bytes - 1] == eol)
{
bytes--;
g_string_append_len (gs, buf, bytes);
break;
}
g_string_append_len (gs, buf, bytes);
}
if (ferror (fp))
fail (false, "Failed reading text: %s", strerror (errno));
*len = gs->len;
return !*len && feof (fp) ? nullptr : gs->str;
}
void
text_options_t::add_options (option_parser_t *parser)
{
GOptionEntry entries[] =
{
{"text", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_text, "Set input text", "string"},
{"text-file", 0, 0, G_OPTION_ARG_STRING, &this->text_file, "Set input text file-name\n\n If no text is provided, standard input is used for input.\n", "filename"},
{"unicodes", 'u', 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_unicodes, "Set input Unicode codepoints", "list of hex numbers"},
{"text-before", 0, 0, G_OPTION_ARG_STRING, &this->text_before, "Set text context before each line", "string"},
{"text-after", 0, 0, G_OPTION_ARG_STRING, &this->text_after, "Set text context after each line", "string"},
{nullptr}
};
parser->add_group (entries,
"text",
"Text options:",
"Options for the input text",
this);
}

View File

@ -1,140 +0,0 @@
/*
* Copyright © 2011 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Google Author(s): Behdad Esfahbod
*/
#include "view-cairo.hh"
#include <assert.h>
void
view_cairo_t::render (const font_options_t *font_opts)
{
bool vertical = HB_DIRECTION_IS_VERTICAL (direction);
int vert = vertical ? 1 : 0;
int horiz = vertical ? 0 : 1;
int x_sign = font_opts->font_size_x < 0 ? -1 : +1;
int y_sign = font_opts->font_size_y < 0 ? -1 : +1;
hb_font_t *font = font_opts->get_font();
view_options_t::font_extents_t extents = view_options.font_extents;
if (!view_options.have_font_extents)
{
hb_font_extents_t hb_extents;
hb_font_get_extents_for_direction (font, direction, &hb_extents);
extents.ascent = scalbn ((double) hb_extents.ascender, scale_bits);
extents.descent = -scalbn ((double) hb_extents.descender, scale_bits);
extents.line_gap = scalbn ((double) hb_extents.line_gap, scale_bits);
}
double ascent = y_sign * extents.ascent;
double descent = y_sign * extents.descent;
double line_gap = y_sign * extents.line_gap + view_options.line_space;
double leading = ascent + descent + line_gap;
/* Calculate surface size. */
double w = 0, h = 0;
(vertical ? w : h) = (int) lines->len * leading - (extents.line_gap + view_options.line_space);
(vertical ? h : w) = 0;
for (unsigned int i = 0; i < lines->len; i++) {
helper_cairo_line_t &line = g_array_index (lines, helper_cairo_line_t, i);
double x_advance, y_advance;
line.get_advance (&x_advance, &y_advance);
if (vertical)
h = MAX (h, y_sign * y_advance);
else
w = MAX (w, x_sign * x_advance);
}
cairo_scaled_font_t *scaled_font = helper_cairo_create_scaled_font (font_opts);
/* See if font needs color. */
cairo_content_t content = CAIRO_CONTENT_ALPHA;
if (helper_cairo_scaled_font_has_color (scaled_font))
content = CAIRO_CONTENT_COLOR;
/* Create surface. */
cairo_t *cr = helper_cairo_create_context (w + view_options.margin.l + view_options.margin.r,
h + view_options.margin.t + view_options.margin.b,
&view_options, &output_options, content);
cairo_set_scaled_font (cr, scaled_font);
/* Setup coordinate system. */
cairo_translate (cr, view_options.margin.l, view_options.margin.t);
if (vertical)
cairo_translate (cr,
w - ascent, /* We currently always stack lines right to left */
y_sign < 0 ? h : 0);
else
{
cairo_translate (cr,
x_sign < 0 ? w : 0,
y_sign < 0 ? descent : ascent);
}
/* Draw. */
cairo_translate (cr, +vert * leading, -horiz * leading);
for (unsigned int i = 0; i < lines->len; i++)
{
helper_cairo_line_t &l = g_array_index (lines, helper_cairo_line_t, i);
cairo_translate (cr, -vert * leading, +horiz * leading);
if (view_options.annotate) {
cairo_save (cr);
/* Draw actual glyph origins */
cairo_set_source_rgba (cr, 1., 0., 0., .5);
cairo_set_line_width (cr, 5);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
for (unsigned i = 0; i < l.num_glyphs; i++) {
cairo_move_to (cr, l.glyphs[i].x, l.glyphs[i].y);
cairo_rel_line_to (cr, 0, 0);
}
cairo_stroke (cr);
cairo_restore (cr);
}
if (0 && cairo_surface_get_type (cairo_get_target (cr)) == CAIRO_SURFACE_TYPE_IMAGE) {
/* cairo_show_glyphs() doesn't support subpixel positioning */
cairo_glyph_path (cr, l.glyphs, l.num_glyphs);
cairo_fill (cr);
} else if (l.num_clusters)
cairo_show_text_glyphs (cr,
l.utf8, l.utf8_len,
l.glyphs, l.num_glyphs,
l.clusters, l.num_clusters,
l.cluster_flags);
else
cairo_show_glyphs (cr, l.glyphs, l.num_glyphs);
}
/* Clean up. */
helper_cairo_destroy_context (cr);
cairo_scaled_font_destroy (scaled_font);
}

View File

@ -27,22 +27,23 @@
#ifndef VIEW_CAIRO_HH
#define VIEW_CAIRO_HH
#include "hb.hh"
#include "options.hh"
#include "view-options.hh"
#include "output-options.hh"
#include "helper-cairo.hh"
struct view_cairo_t
struct view_cairo_t : view_options_t, output_options_t
{
view_cairo_t (option_parser_t *parser)
: output_options (parser, helper_cairo_supported_formats),
view_options (parser),
direction (HB_DIRECTION_INVALID),
lines (0), scale_bits (0) {}
~view_cairo_t () {
~view_cairo_t ()
{
cairo_debug_reset_static_data ();
}
void add_options (option_parser_t *parser)
{
view_options_t::add_options (parser);
output_options_t::add_options (parser, helper_cairo_supported_formats);
}
void init (hb_buffer_t *buffer, const font_options_t *font_opts)
{
lines = g_array_new (false, false, sizeof (helper_cairo_line_t));
@ -82,14 +83,123 @@ struct view_cairo_t
protected:
output_options_t output_options;
view_options_t view_options;
void render (const font_options_t *font_opts);
hb_direction_t direction; // Remove this, make segment_properties accessible
GArray *lines;
int scale_bits;
hb_direction_t direction = HB_DIRECTION_INVALID; // Remove this, make segment_properties accessible
GArray *lines = nullptr;
int scale_bits = 0;
};
inline void
view_cairo_t::render (const font_options_t *font_opts)
{
bool vertical = HB_DIRECTION_IS_VERTICAL (direction);
int vert = vertical ? 1 : 0;
int horiz = vertical ? 0 : 1;
int x_sign = font_opts->font_size_x < 0 ? -1 : +1;
int y_sign = font_opts->font_size_y < 0 ? -1 : +1;
hb_font_t *font = font_opts->get_font();
if (!have_font_extents)
{
hb_font_extents_t hb_extents;
hb_font_get_extents_for_direction (font, direction, &hb_extents);
font_extents.ascent = scalbn ((double) hb_extents.ascender, scale_bits);
font_extents.descent = -scalbn ((double) hb_extents.descender, scale_bits);
font_extents.line_gap = scalbn ((double) hb_extents.line_gap, scale_bits);
have_font_extents = true;
}
double ascent = y_sign * font_extents.ascent;
double descent = y_sign * font_extents.descent;
double line_gap = y_sign * font_extents.line_gap + line_space;
double leading = ascent + descent + line_gap;
/* Calculate surface size. */
double w = 0, h = 0;
(vertical ? w : h) = (int) lines->len * leading - (font_extents.line_gap + line_space);
(vertical ? h : w) = 0;
for (unsigned int i = 0; i < lines->len; i++) {
helper_cairo_line_t &line = g_array_index (lines, helper_cairo_line_t, i);
double x_advance, y_advance;
line.get_advance (&x_advance, &y_advance);
if (vertical)
h = MAX (h, y_sign * y_advance);
else
w = MAX (w, x_sign * x_advance);
}
cairo_scaled_font_t *scaled_font = helper_cairo_create_scaled_font (font_opts);
/* See if font needs color. */
cairo_content_t content = CAIRO_CONTENT_ALPHA;
if (helper_cairo_scaled_font_has_color (scaled_font))
content = CAIRO_CONTENT_COLOR;
/* Create surface. */
cairo_t *cr = helper_cairo_create_context (w + margin.l + margin.r,
h + margin.t + margin.b,
static_cast<view_options_t *> (this),
static_cast<output_options_t *> (this),
content);
cairo_set_scaled_font (cr, scaled_font);
/* Setup coordinate system. */
cairo_translate (cr, margin.l, margin.t);
if (vertical)
cairo_translate (cr,
w - ascent, /* We currently always stack lines right to left */
y_sign < 0 ? h : 0);
else
{
cairo_translate (cr,
x_sign < 0 ? w : 0,
y_sign < 0 ? descent : ascent);
}
/* Draw. */
cairo_translate (cr, +vert * leading, -horiz * leading);
for (unsigned int i = 0; i < lines->len; i++)
{
helper_cairo_line_t &l = g_array_index (lines, helper_cairo_line_t, i);
cairo_translate (cr, -vert * leading, +horiz * leading);
if (annotate) {
cairo_save (cr);
/* Draw actual glyph origins */
cairo_set_source_rgba (cr, 1., 0., 0., .5);
cairo_set_line_width (cr, 5);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
for (unsigned i = 0; i < l.num_glyphs; i++) {
cairo_move_to (cr, l.glyphs[i].x, l.glyphs[i].y);
cairo_rel_line_to (cr, 0, 0);
}
cairo_stroke (cr);
cairo_restore (cr);
}
if (0 && cairo_surface_get_type (cairo_get_target (cr)) == CAIRO_SURFACE_TYPE_IMAGE) {
/* cairo_show_glyphs() doesn't support subpixel positioning */
cairo_glyph_path (cr, l.glyphs, l.num_glyphs);
cairo_fill (cr);
} else if (l.num_clusters)
cairo_show_text_glyphs (cr,
l.utf8, l.utf8_len,
l.glyphs, l.num_glyphs,
l.clusters, l.num_clusters,
l.cluster_flags);
else
cairo_show_glyphs (cr, l.glyphs, l.num_glyphs);
}
/* Clean up. */
helper_cairo_destroy_context (cr);
cairo_scaled_font_destroy (scaled_font);
}
#endif

123
util/view-options.hh Normal file
View File

@ -0,0 +1,123 @@
/*
* Copyright © 2011 Google, Inc.
*
* This is part of HarfBuzz, a text shaping library.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Google Author(s): Behdad Esfahbod
*/
#ifndef VIEW_OPTIONS_HH
#define VIEW_OPTIONS_HH
#include "options.hh"
#define DEFAULT_MARGIN 16
#define DEFAULT_FORE "#000000"
#define DEFAULT_BACK "#FFFFFF"
struct view_options_t
{
~view_options_t ()
{
g_free (fore);
g_free (back);
}
void add_options (option_parser_t *parser);
hb_bool_t annotate = false;
char *fore = nullptr;
char *back = nullptr;
double line_space = 0;
bool have_font_extents = false;
struct font_extents_t {
double ascent, descent, line_gap;
} font_extents = {0., 0., 0.};
struct margin_t {
double t, r, b, l;
} margin = {DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN};
};
static gboolean
parse_font_extents (const char *name G_GNUC_UNUSED,
const char *arg,
gpointer data,
GError **error G_GNUC_UNUSED)
{
view_options_t *view_opts = (view_options_t *) data;
view_options_t::font_extents_t &e = view_opts->font_extents;
switch (sscanf (arg, "%lf%*[ ,]%lf%*[ ,]%lf", &e.ascent, &e.descent, &e.line_gap)) {
case 1: HB_FALLTHROUGH;
case 2: HB_FALLTHROUGH;
case 3:
view_opts->have_font_extents = true;
return true;
default:
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"%s argument should be one to three space-separated numbers",
name);
return false;
}
}
static gboolean
parse_margin (const char *name G_GNUC_UNUSED,
const char *arg,
gpointer data,
GError **error G_GNUC_UNUSED)
{
view_options_t *view_opts = (view_options_t *) data;
view_options_t::margin_t &m = view_opts->margin;
switch (sscanf (arg, "%lf%*[ ,]%lf%*[ ,]%lf%*[ ,]%lf", &m.t, &m.r, &m.b, &m.l)) {
case 1: m.r = m.t; HB_FALLTHROUGH;
case 2: m.b = m.t; HB_FALLTHROUGH;
case 3: m.l = m.r; HB_FALLTHROUGH;
case 4: return true;
default:
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"%s argument should be one to four space-separated numbers",
name);
return false;
}
}
void
view_options_t::add_options (option_parser_t *parser)
{
GOptionEntry entries[] =
{
{"annotate", 0, 0, G_OPTION_ARG_NONE, &this->annotate, "Annotate output rendering", nullptr},
{"background", 0, 0, G_OPTION_ARG_STRING, &this->back, "Set background color (default: " DEFAULT_BACK ")", "rrggbb/rrggbbaa"},
{"foreground", 0, 0, G_OPTION_ARG_STRING, &this->fore, "Set foreground color (default: " DEFAULT_FORE ")", "rrggbb/rrggbbaa"},
{"line-space", 0, 0, G_OPTION_ARG_DOUBLE, &this->line_space, "Set space between lines (default: 0)", "units"},
{"font-extents", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_font_extents, "Set font ascent/descent/line-gap (default: auto)","one to three numbers"},
{"margin", 0, 0, G_OPTION_ARG_CALLBACK, (gpointer) &parse_margin, "Margin around output (default: " G_STRINGIFY(DEFAULT_MARGIN) ")","one to four numbers"},
{nullptr}
};
parser->add_group (entries,
"view",
"View options:",
"Options for output rendering",
this);
}
#endif