2012-09-04 15:33:15 +00:00
|
|
|
/*
|
2012-12-11 12:48:01 +00:00
|
|
|
* TextLineBuffer.cc
|
2012-09-04 15:33:15 +00:00
|
|
|
*
|
|
|
|
* Generate and optimized HTML for one line
|
|
|
|
*
|
2013-02-05 06:36:36 +00:00
|
|
|
* Copyright (C) 2012,2013 Lu Wang <coolwanglu@gmail.com>
|
2012-09-04 15:33:15 +00:00
|
|
|
*/
|
|
|
|
|
2012-09-05 07:13:21 +00:00
|
|
|
#include <vector>
|
2013-03-21 04:18:26 +00:00
|
|
|
#include <cmath>
|
2013-03-20 15:46:58 +00:00
|
|
|
#include <algorithm>
|
2012-09-05 07:13:21 +00:00
|
|
|
|
2012-09-04 15:33:15 +00:00
|
|
|
#include "HTMLRenderer.h"
|
2012-12-11 12:48:01 +00:00
|
|
|
#include "TextLineBuffer.h"
|
2012-11-29 09:28:05 +00:00
|
|
|
#include "util/namespace.h"
|
2012-11-29 09:45:26 +00:00
|
|
|
#include "util/unicode.h"
|
2012-11-29 10:28:07 +00:00
|
|
|
#include "util/math.h"
|
2013-02-27 18:11:34 +00:00
|
|
|
#include "util/css_const.h"
|
2013-02-15 05:07:00 +00:00
|
|
|
#include "util/encoding.h"
|
2012-09-04 15:33:15 +00:00
|
|
|
|
2012-09-12 15:26:14 +00:00
|
|
|
namespace pdf2htmlEX {
|
|
|
|
|
2012-09-04 15:33:15 +00:00
|
|
|
using std::min;
|
|
|
|
using std::max;
|
2012-09-05 07:13:21 +00:00
|
|
|
using std::vector;
|
2012-09-10 17:53:33 +00:00
|
|
|
using std::ostream;
|
2012-11-29 10:28:07 +00:00
|
|
|
using std::cerr;
|
|
|
|
using std::endl;
|
2013-03-20 15:46:58 +00:00
|
|
|
using std::find;
|
2013-03-21 04:18:26 +00:00
|
|
|
using std::abs;
|
2012-09-04 15:33:15 +00:00
|
|
|
|
2012-12-11 12:48:01 +00:00
|
|
|
void HTMLRenderer::TextLineBuffer::reset(GfxState * state)
|
2012-09-04 15:33:15 +00:00
|
|
|
{
|
|
|
|
state->transform(state->getCurX(), state->getCurY(), &x, &y);
|
2013-02-05 12:37:05 +00:00
|
|
|
tm_id = renderer->transform_matrix_manager.get_id();
|
2012-09-04 15:33:15 +00:00
|
|
|
}
|
|
|
|
|
2012-12-11 12:48:01 +00:00
|
|
|
void HTMLRenderer::TextLineBuffer::append_unicodes(const Unicode * u, int l)
|
2012-09-04 15:33:15 +00:00
|
|
|
{
|
|
|
|
text.insert(text.end(), u, u+l);
|
|
|
|
}
|
|
|
|
|
2012-12-11 12:48:01 +00:00
|
|
|
void HTMLRenderer::TextLineBuffer::append_offset(double width)
|
2012-09-04 15:33:15 +00:00
|
|
|
{
|
|
|
|
if((!offsets.empty()) && (offsets.back().start_idx == text.size()))
|
|
|
|
offsets.back().width += width;
|
|
|
|
else
|
2012-09-09 06:48:10 +00:00
|
|
|
offsets.push_back(Offset({text.size(), width}));
|
2012-09-04 15:33:15 +00:00
|
|
|
}
|
|
|
|
|
2012-12-11 12:48:01 +00:00
|
|
|
void HTMLRenderer::TextLineBuffer::append_state(void)
|
2012-09-04 15:33:15 +00:00
|
|
|
{
|
|
|
|
if(states.empty() || (states.back().start_idx != text.size()))
|
|
|
|
{
|
|
|
|
states.resize(states.size() + 1);
|
|
|
|
states.back().start_idx = text.size();
|
2013-03-20 15:46:58 +00:00
|
|
|
states.back().hash_umask = 0;
|
2012-09-04 15:33:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
set_state(states.back());
|
|
|
|
}
|
|
|
|
|
2012-12-11 12:48:01 +00:00
|
|
|
void HTMLRenderer::TextLineBuffer::flush(void)
|
2012-09-04 15:33:15 +00:00
|
|
|
{
|
|
|
|
/*
|
2012-12-11 12:52:36 +00:00
|
|
|
* Each Line is an independent absolute positioned block
|
2012-09-04 15:33:15 +00:00
|
|
|
* so even we have a few states or offsets, we may omit them
|
|
|
|
*/
|
|
|
|
if(text.empty()) return;
|
|
|
|
|
|
|
|
if(states.empty() || (states[0].start_idx != 0))
|
|
|
|
{
|
|
|
|
cerr << "Warning: text without a style! Must be a bug in pdf2htmlEX" << endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-03-20 15:46:58 +00:00
|
|
|
optimize();
|
|
|
|
|
2012-09-04 15:33:15 +00:00
|
|
|
double max_ascent = 0;
|
2012-09-09 06:48:10 +00:00
|
|
|
for(auto iter = states.begin(); iter != states.end(); ++iter)
|
|
|
|
{
|
|
|
|
const auto & s = *iter;
|
2013-03-20 15:46:58 +00:00
|
|
|
max_ascent = max<double>(max_ascent, s.font_info->ascent * s.draw_font_size);
|
2012-09-09 06:48:10 +00:00
|
|
|
}
|
2012-09-04 15:33:15 +00:00
|
|
|
|
2013-03-21 04:18:26 +00:00
|
|
|
// append a dummy state for convenience
|
|
|
|
states.resize(states.size() + 1);
|
|
|
|
states.back().start_idx = text.size();
|
|
|
|
|
|
|
|
for(auto iter = states.begin(); iter != states.end(); ++iter)
|
|
|
|
iter->hash();
|
|
|
|
|
|
|
|
// append a dummy offset for convenience
|
|
|
|
offsets.push_back(Offset({text.size(), 0}));
|
|
|
|
|
2013-01-28 10:46:44 +00:00
|
|
|
ostream & out = renderer->f_pages.fs;
|
2013-02-05 06:51:00 +00:00
|
|
|
renderer->height_manager.install(max_ascent);
|
2013-02-06 12:09:40 +00:00
|
|
|
renderer->left_manager .install(x);
|
|
|
|
renderer->bottom_manager.install(y);
|
|
|
|
|
|
|
|
out << "<div class=\"" << CSS::LINE_CN
|
|
|
|
<< " " << CSS::TRANSFORM_MATRIX_CN << tm_id
|
|
|
|
<< " " << CSS::LEFT_CN << renderer->left_manager .get_id()
|
|
|
|
<< " " << CSS::HEIGHT_CN << renderer->height_manager.get_id()
|
|
|
|
<< " " << CSS::BOTTOM_CN << renderer->bottom_manager.get_id()
|
2012-09-16 07:53:41 +00:00
|
|
|
<< "\">";
|
2012-09-04 15:33:15 +00:00
|
|
|
|
|
|
|
auto cur_state_iter = states.begin();
|
|
|
|
auto cur_offset_iter = offsets.begin();
|
|
|
|
|
2012-09-05 07:13:21 +00:00
|
|
|
//accumulated horizontal offset;
|
2012-09-04 15:33:15 +00:00
|
|
|
double dx = 0;
|
|
|
|
|
2012-09-06 06:37:09 +00:00
|
|
|
stack.clear();
|
|
|
|
stack.push_back(nullptr);
|
|
|
|
|
|
|
|
// whenever a negative offset appears, we should not pop out that <span>
|
|
|
|
// otherwise the effect of negative margin-left would disappear
|
2012-09-06 11:05:49 +00:00
|
|
|
size_t last_text_pos_with_negative_offset = 0;
|
2012-09-05 07:13:21 +00:00
|
|
|
|
2012-09-04 15:33:15 +00:00
|
|
|
size_t cur_text_idx = 0;
|
|
|
|
while(cur_text_idx < text.size())
|
|
|
|
{
|
|
|
|
if(cur_text_idx >= cur_state_iter->start_idx)
|
|
|
|
{
|
2012-09-06 06:37:09 +00:00
|
|
|
// greedy
|
|
|
|
int best_cost = State::ID_COUNT;
|
|
|
|
|
|
|
|
// we have a nullptr at the beginning, so no need to check for rend
|
|
|
|
for(auto iter = stack.rbegin(); *iter; ++iter)
|
2012-09-05 07:13:21 +00:00
|
|
|
{
|
2012-09-06 06:37:09 +00:00
|
|
|
int cost = cur_state_iter->diff(**iter);
|
|
|
|
if(cost < best_cost)
|
|
|
|
{
|
|
|
|
while(stack.back() != *iter)
|
|
|
|
{
|
|
|
|
stack.back()->end(out);
|
|
|
|
stack.pop_back();
|
|
|
|
}
|
|
|
|
best_cost = cost;
|
|
|
|
|
|
|
|
if(best_cost == 0)
|
|
|
|
break;
|
|
|
|
}
|
2012-09-05 07:13:21 +00:00
|
|
|
|
2012-09-06 06:37:09 +00:00
|
|
|
// cannot go further
|
|
|
|
if((*iter)->start_idx <= last_text_pos_with_negative_offset)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
cur_state_iter->begin(out, stack.back());
|
|
|
|
stack.push_back(&*cur_state_iter);
|
2012-09-04 15:33:15 +00:00
|
|
|
|
|
|
|
++ cur_state_iter;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(cur_text_idx >= cur_offset_iter->start_idx)
|
|
|
|
{
|
|
|
|
double target = cur_offset_iter->width + dx;
|
|
|
|
|
2013-03-21 04:18:26 +00:00
|
|
|
if(equal(target, stack.back()->single_space_offset()))
|
|
|
|
{
|
|
|
|
Unicode u = ' ';
|
|
|
|
outputUnicodes(out, &u, 1);
|
|
|
|
dx = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto & wm = renderer->whitespace_manager;
|
|
|
|
wm.install(target);
|
|
|
|
auto wid = wm.get_id();
|
|
|
|
double w = wm.get_actual_value();
|
2012-09-04 15:33:15 +00:00
|
|
|
|
2013-03-21 04:18:26 +00:00
|
|
|
if(w < 0)
|
|
|
|
last_text_pos_with_negative_offset = cur_text_idx;
|
2012-09-06 06:37:09 +00:00
|
|
|
|
2013-03-21 04:18:26 +00:00
|
|
|
auto * p = stack.back();
|
|
|
|
double threshold = p->draw_font_size * (p->font_info->ascent - p->font_info->descent) * (renderer->param->space_threshold);
|
2012-09-07 00:39:21 +00:00
|
|
|
|
2013-03-21 04:18:26 +00:00
|
|
|
out << "<span class=\"" << CSS::WHITESPACE_CN
|
|
|
|
<< ' ' << CSS::WHITESPACE_CN << wid << "\">" << (target > (threshold - EPS) ? " " : "") << "</span>";
|
2012-09-04 15:33:15 +00:00
|
|
|
|
2013-03-21 04:18:26 +00:00
|
|
|
dx = target - w;
|
|
|
|
}
|
2012-09-04 15:33:15 +00:00
|
|
|
|
|
|
|
++ cur_offset_iter;
|
|
|
|
}
|
|
|
|
|
2012-10-02 18:19:40 +00:00
|
|
|
size_t next_text_idx = min<size_t>(cur_state_iter->start_idx, cur_offset_iter->start_idx);
|
2012-09-10 19:01:02 +00:00
|
|
|
|
|
|
|
outputUnicodes(out, (&text.front()) + cur_text_idx, next_text_idx - cur_text_idx);
|
2012-09-04 15:33:15 +00:00
|
|
|
cur_text_idx = next_text_idx;
|
|
|
|
}
|
|
|
|
|
2012-09-05 07:13:21 +00:00
|
|
|
// we have a nullptr in the bottom
|
2012-09-06 06:37:09 +00:00
|
|
|
while(stack.back())
|
2012-09-05 07:13:21 +00:00
|
|
|
{
|
2012-09-06 06:37:09 +00:00
|
|
|
stack.back()->end(out);
|
|
|
|
stack.pop_back();
|
2012-09-05 07:13:21 +00:00
|
|
|
}
|
|
|
|
|
2012-09-04 15:33:15 +00:00
|
|
|
out << "</div>";
|
|
|
|
|
|
|
|
|
|
|
|
states.clear();
|
|
|
|
offsets.clear();
|
|
|
|
text.clear();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2012-12-11 12:48:01 +00:00
|
|
|
void HTMLRenderer::TextLineBuffer::set_state (State & state)
|
2012-09-04 15:33:15 +00:00
|
|
|
{
|
|
|
|
state.ids[State::FONT_ID] = renderer->cur_font_info->id;
|
2013-02-05 06:36:36 +00:00
|
|
|
state.ids[State::FONT_SIZE_ID] = renderer->font_size_manager.get_id();
|
2013-02-05 13:56:19 +00:00
|
|
|
state.ids[State::FILL_COLOR_ID] = renderer->fill_color_manager.get_id();
|
|
|
|
state.ids[State::STROKE_COLOR_ID] = renderer->stroke_color_manager.get_id();
|
2013-02-05 06:36:36 +00:00
|
|
|
state.ids[State::LETTER_SPACE_ID] = renderer->letter_space_manager.get_id();
|
|
|
|
state.ids[State::WORD_SPACE_ID] = renderer->word_space_manager.get_id();
|
2013-02-05 06:45:40 +00:00
|
|
|
state.ids[State::RISE_ID] = renderer->rise_manager.get_id();
|
2012-09-04 15:33:15 +00:00
|
|
|
|
2013-03-20 15:46:58 +00:00
|
|
|
state.font_info = renderer->cur_font_info;
|
2013-03-21 04:18:26 +00:00
|
|
|
state.draw_font_size = renderer->font_size_manager.get_actual_value();
|
|
|
|
state.letter_space = renderer->letter_space_manager.get_actual_value();
|
|
|
|
state.word_space = renderer->word_space_manager.get_actual_value();
|
2012-09-04 15:33:15 +00:00
|
|
|
}
|
|
|
|
|
2013-03-20 15:46:58 +00:00
|
|
|
void HTMLRenderer::TextLineBuffer::optimize(void)
|
|
|
|
{
|
2013-03-21 14:29:38 +00:00
|
|
|
// this function needs more work
|
|
|
|
return;
|
|
|
|
|
2013-03-20 15:46:58 +00:00
|
|
|
assert(!states.empty());
|
|
|
|
|
|
|
|
// set proper hash_umask
|
2013-03-21 04:18:26 +00:00
|
|
|
long long word_space_umask = ((long long)0xff) << (8*((int)State::WORD_SPACE_ID));
|
|
|
|
for(auto iter = states.begin(); iter != states.end(); ++iter)
|
|
|
|
{
|
|
|
|
auto text_iter1 = text.begin() + (iter->start_idx);
|
|
|
|
auto next_iter = iter;
|
|
|
|
++next_iter;
|
|
|
|
auto text_iter2 = (next_iter == states.end()) ? (text.end()) : (text.begin() + (next_iter->start_idx));
|
|
|
|
if(find(text_iter1, text_iter2, ' ') == text_iter2)
|
|
|
|
{
|
|
|
|
// if there's no space, word_space does not matter;
|
|
|
|
iter->hash_umask |= word_space_umask;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// clean zero offsets
|
|
|
|
{
|
|
|
|
auto write_iter = offsets.begin();
|
|
|
|
for(auto iter = offsets.begin(); iter != offsets.end(); ++iter)
|
|
|
|
{
|
|
|
|
if(!equal(iter->width, 0))
|
|
|
|
{
|
|
|
|
*write_iter = *iter;
|
|
|
|
++write_iter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
offsets.erase(write_iter, offsets.end());
|
|
|
|
}
|
2013-03-20 15:46:58 +00:00
|
|
|
|
|
|
|
// In some PDF files all spaces are converted into positionig shifts
|
|
|
|
// We may try to change them to ' ' and adjusted word_spaces
|
|
|
|
// This can also be applied when param->space_as_offset is set
|
|
|
|
|
2013-03-21 04:18:26 +00:00
|
|
|
// for now, we cosider only the no-space scenario
|
|
|
|
if(offsets.size() > 0)
|
|
|
|
{
|
|
|
|
// Since GCC 4.4.6 is suported, I cannot use all_of + lambda here
|
|
|
|
bool all_ws_umask = true;
|
|
|
|
for(auto iter = states.begin(); iter != states.end(); ++iter)
|
|
|
|
{
|
|
|
|
if(!(iter->hash_umask & word_space_umask))
|
|
|
|
{
|
|
|
|
all_ws_umask = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(all_ws_umask)
|
|
|
|
{
|
|
|
|
double avg_width = 0;
|
|
|
|
int posive_offset_count = 0;
|
|
|
|
for(auto iter = offsets.begin(); iter != offsets.end(); ++iter)
|
|
|
|
{
|
|
|
|
if(is_positive(iter->width))
|
|
|
|
{
|
|
|
|
++posive_offset_count;
|
|
|
|
avg_width += iter->width;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
avg_width /= posive_offset_count;
|
|
|
|
|
|
|
|
// now check if the width of offsets are close enough
|
|
|
|
// TODO: it might make more sense if the threshold is proportion to the font size
|
|
|
|
bool ok = true;
|
|
|
|
double accum_off = 0;
|
|
|
|
double orig_accum_off = 0;
|
|
|
|
for(auto iter = offsets.begin(); iter != offsets.end(); ++iter)
|
|
|
|
{
|
|
|
|
orig_accum_off += iter->width;
|
|
|
|
accum_off += avg_width;
|
|
|
|
if(is_positive(iter->width) && abs(orig_accum_off - accum_off) >= renderer->param->h_eps)
|
|
|
|
{
|
|
|
|
ok = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(ok)
|
|
|
|
{
|
|
|
|
// ok, make all offsets equi-width
|
|
|
|
for(auto iter = offsets.begin(); iter != offsets.end(); ++iter)
|
|
|
|
{
|
|
|
|
if(is_positive(iter->width))
|
|
|
|
iter->width = avg_width;
|
|
|
|
}
|
|
|
|
// set new word_space
|
|
|
|
for(auto iter = states.begin(); iter != states.end(); ++iter)
|
|
|
|
{
|
2013-03-21 04:27:07 +00:00
|
|
|
double new_word_space = avg_width - iter->single_space_offset() + iter->word_space;
|
2013-03-21 04:18:26 +00:00
|
|
|
|
|
|
|
// install new word_space
|
|
|
|
// we might introduce more variance here
|
|
|
|
auto & wm = renderer->word_space_manager;
|
|
|
|
wm.install(new_word_space);
|
|
|
|
iter->ids[State::WORD_SPACE_ID] = wm.get_id();
|
|
|
|
iter->word_space = wm.get_actual_value();
|
|
|
|
iter->hash_umask &= (~word_space_umask);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-03-20 15:46:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// this state will be converted to a child node of the node of prev_state
|
|
|
|
// dump the difference between previous state
|
|
|
|
// also clone corresponding states
|
2012-12-11 12:48:01 +00:00
|
|
|
void HTMLRenderer::TextLineBuffer::State::begin (ostream & out, const State * prev_state)
|
2012-09-05 07:13:21 +00:00
|
|
|
{
|
2013-03-20 15:46:58 +00:00
|
|
|
long long cur_mask = 0xff;
|
2012-09-05 07:13:21 +00:00
|
|
|
bool first = true;
|
2013-03-20 15:46:58 +00:00
|
|
|
for(int i = 0; i < ID_COUNT; ++i, cur_mask<<=8)
|
2012-09-04 15:33:15 +00:00
|
|
|
{
|
2013-03-20 15:46:58 +00:00
|
|
|
if(hash_umask & cur_mask) // we don't care about this ID
|
|
|
|
{
|
|
|
|
if (prev_state && (!(prev_state->hash_umask & cur_mask))) // if prev_state have it set
|
|
|
|
{
|
|
|
|
// we have to inherit it
|
|
|
|
ids[i] = prev_state->ids[i];
|
|
|
|
hash_umask &= (~cur_mask);
|
|
|
|
}
|
|
|
|
//anyway we don't have to output it
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// now we care about the ID
|
|
|
|
if(prev_state && (!(prev_state->hash_umask & cur_mask)) && (prev_state->ids[i] == ids[i]))
|
2012-09-05 07:13:21 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if(first)
|
|
|
|
{
|
2012-09-06 06:37:09 +00:00
|
|
|
out << "<span class=\"";
|
2012-09-05 07:13:21 +00:00
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
out << ' ';
|
|
|
|
}
|
|
|
|
|
2013-01-31 22:21:57 +00:00
|
|
|
// out should have hex set
|
2013-02-05 13:56:19 +00:00
|
|
|
out << css_class_names[i];
|
2013-01-31 22:21:57 +00:00
|
|
|
if (ids[i] == -1)
|
2013-02-05 13:56:19 +00:00
|
|
|
out << CSS::INVALID_ID;
|
2013-01-31 22:21:57 +00:00
|
|
|
else
|
2013-02-05 13:56:19 +00:00
|
|
|
out << ids[i];
|
2012-09-04 15:33:15 +00:00
|
|
|
}
|
2012-09-05 07:13:21 +00:00
|
|
|
|
2013-03-20 15:46:58 +00:00
|
|
|
if(first) // we actually just inherit the whole prev_state
|
2012-09-06 06:37:09 +00:00
|
|
|
{
|
|
|
|
need_close = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
out << "\">";
|
|
|
|
need_close = true;
|
|
|
|
}
|
2012-09-04 15:33:15 +00:00
|
|
|
}
|
|
|
|
|
2012-12-11 12:48:01 +00:00
|
|
|
void HTMLRenderer::TextLineBuffer::State::end(ostream & out) const
|
2012-09-05 07:13:21 +00:00
|
|
|
{
|
|
|
|
if(need_close)
|
|
|
|
out << "</span>";
|
|
|
|
}
|
|
|
|
|
2012-12-11 12:48:01 +00:00
|
|
|
void HTMLRenderer::TextLineBuffer::State::hash(void)
|
2012-09-05 07:13:21 +00:00
|
|
|
{
|
|
|
|
hash_value = 0;
|
|
|
|
for(int i = 0; i < ID_COUNT; ++i)
|
|
|
|
{
|
|
|
|
hash_value = (hash_value << 8) | (ids[i] & 0xff);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-11 12:48:01 +00:00
|
|
|
int HTMLRenderer::TextLineBuffer::State::diff(const State & s) const
|
2012-09-04 15:33:15 +00:00
|
|
|
{
|
2012-09-05 07:13:21 +00:00
|
|
|
/*
|
|
|
|
* A quick check based on hash_value
|
|
|
|
* it could be wrong when there are more then 256 classes,
|
2012-09-05 08:19:01 +00:00
|
|
|
* in which case the output may not be optimal, but still 'correct' in terms of HTML
|
2012-09-05 07:13:21 +00:00
|
|
|
*/
|
2013-03-20 15:46:58 +00:00
|
|
|
long long common_mask = ~(hash_umask | s.hash_umask);
|
|
|
|
if((hash_value & common_mask) == (s.hash_value & common_mask)) return 0;
|
2012-09-05 07:13:21 +00:00
|
|
|
|
2013-03-20 15:46:58 +00:00
|
|
|
long long cur_mask = 0xff;
|
2012-09-05 07:13:21 +00:00
|
|
|
int d = 0;
|
|
|
|
for(int i = 0; i < ID_COUNT; ++i)
|
2013-03-20 15:46:58 +00:00
|
|
|
{
|
|
|
|
if((common_mask & cur_mask) && (ids[i] != s.ids[i]))
|
2012-09-05 07:13:21 +00:00
|
|
|
++ d;
|
2013-03-20 15:46:58 +00:00
|
|
|
cur_mask <<= 8;
|
|
|
|
}
|
2012-09-05 07:13:21 +00:00
|
|
|
return d;
|
2012-09-04 15:33:15 +00:00
|
|
|
}
|
|
|
|
|
2013-03-21 04:18:26 +00:00
|
|
|
double HTMLRenderer::TextLineBuffer::State::single_space_offset(void) const
|
|
|
|
{
|
2013-03-21 04:27:07 +00:00
|
|
|
return word_space + letter_space + font_info->space_width * draw_font_size;
|
2013-03-21 04:18:26 +00:00
|
|
|
}
|
|
|
|
|
2013-02-05 10:19:25 +00:00
|
|
|
// the order should be the same as in the enum
|
|
|
|
const char * const HTMLRenderer::TextLineBuffer::State::css_class_names [] = {
|
2013-02-28 07:59:14 +00:00
|
|
|
CSS::FONT_FAMILY_CN,
|
2013-02-05 10:19:25 +00:00
|
|
|
CSS::FONT_SIZE_CN,
|
|
|
|
CSS::FILL_COLOR_CN,
|
|
|
|
CSS::STROKE_COLOR_CN,
|
|
|
|
CSS::LETTER_SPACE_CN,
|
|
|
|
CSS::WORD_SPACE_CN,
|
|
|
|
CSS::RISE_CN
|
|
|
|
};
|
|
|
|
|
2012-09-12 15:26:14 +00:00
|
|
|
} //namespace pdf2htmlEX
|